diff --git a/.config/taplo.toml b/.config/taplo.toml index f5d0b7021ba898ea3ab96323fa3fbc4efdd7b307..2c6ccfb2b34440686764c39ed6db1c73ed940f06 100644 --- a/.config/taplo.toml +++ b/.config/taplo.toml @@ -2,10 +2,12 @@ # ignore zombienet as they do some deliberate custom toml stuff exclude = [ + "bridges/testing/**", "cumulus/zombienet/**", "polkadot/node/malus/integrationtests/**", "polkadot/zombienet_tests/**", "substrate/zombienet/**", + "target/**", ] # global rules diff --git a/.github/scripts/check-prdoc.py b/.github/scripts/check-prdoc.py new file mode 100644 index 0000000000000000000000000000000000000000..42b063f2885da148033986dfa49740f2b0416460 --- /dev/null +++ b/.github/scripts/check-prdoc.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +''' +Ensure that the prdoc files are valid. + +# Example + +```sh +python3 -m pip install cargo-workspace +python3 .github/scripts/check-prdoc.py Cargo.toml prdoc/*.prdoc +``` + +Produces example output: +```pre +🔎 Reading workspace polkadot-sdk/Cargo.toml +📦 Checking 32 prdocs against 493 crates. +✅ All prdocs are valid +``` +''' + +import os +import yaml +import argparse +import cargo_workspace + +def check_prdoc_crate_names(root, paths): + ''' + Check that all crates of the `crates` section of each prdoc is present in the workspace. + ''' + + print(f'🔎 Reading workspace {root}.') + workspace = cargo_workspace.Workspace.from_path(root) + crate_names = [crate.name for crate in workspace.crates] + + print(f'📦 Checking {len(paths)} prdocs against {len(crate_names)} crates.') + faulty = {} + + for path in paths: + with open(path, 'r') as f: + prdoc = yaml.safe_load(f) + + for crate in prdoc.get('crates', []): + crate = crate['name'] + if crate in crate_names: + continue + + faulty.setdefault(path, []).append(crate) + + if len(faulty) == 0: + print('✅ All prdocs are valid.') + else: + print('❌ Some prdocs are invalid.') + for path, crates in faulty.items(): + print(f'💥 {path} lists invalid crate: {", ".join(crates)}') + exit(1) + +def parse_args(): + parser = argparse.ArgumentParser(description='Check prdoc files') + parser.add_argument('root', help='The cargo workspace manifest', metavar='root', type=str, nargs=1) + parser.add_argument('prdoc', help='The prdoc files', metavar='prdoc', type=str, nargs='*') + args = parser.parse_args() + + if len(args.prdoc) == 0: + print('❌ Need at least one prdoc file as argument.') + exit(1) + + return { 'root': os.path.abspath(args.root[0]), 'prdocs': args.prdoc } + +if __name__ == '__main__': + args = parse_args() + check_prdoc_crate_names(args['root'], args['prdocs']) diff --git a/.github/workflows/check-labels.yml b/.github/workflows/check-labels.yml index 97562f0da09569931582864bd764e6724900d619..1d1a8770058d33ba5e449c6a2e8b307e0ff02eb7 100644 --- a/.github/workflows/check-labels.yml +++ b/.github/workflows/check-labels.yml @@ -9,14 +9,6 @@ jobs: check-labels: runs-on: ubuntu-latest steps: - - name: Skip merge queue - if: ${{ contains(github.ref, 'gh-readonly-queue') }} - run: exit 0 - - name: Pull image - env: - IMAGE: paritytech/ruled_labels:0.4.0 - run: docker pull $IMAGE - - name: Check labels env: IMAGE: paritytech/ruled_labels:0.4.0 @@ -28,6 +20,16 @@ jobs: RULES_PATH: labels/ruled_labels CHECK_SPECS: "specs_polkadot-sdk.yaml" run: | + if [ ${{ github.ref }} == "refs/heads/master" ]; then + echo "Skipping master" + exit 0 + fi + if [ $(echo ${{ github.ref }} | grep -c "gh-readonly-queue") -eq 1 ]; then + echo "Skipping merge queue" + exit 0 + fi + + docker pull $IMAGE echo "REPO: ${REPO}" echo "GITHUB_PR: ${GITHUB_PR}" diff --git a/.github/workflows/check-prdoc.yml b/.github/workflows/check-prdoc.yml index f47404744a49b86735b584e5c0f84bda3fe3078e..c31dee06ec54a0154efc3ad46ff24c79de4d0d7b 100644 --- a/.github/workflows/check-prdoc.yml +++ b/.github/workflows/check-prdoc.yml @@ -17,31 +17,25 @@ env: jobs: check-prdoc: runs-on: ubuntu-latest + if: github.event.pull_request.number != '' steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 # we cannot show the version in this step (ie before checking out the repo) # due to https://github.com/paritytech/prdoc/issues/15 - - name: Skip merge queue - if: ${{ contains(github.ref, 'gh-readonly-queue') }} - run: exit 0 - - name: Pull image + - name: Check if PRdoc is required + id: get-labels run: | echo "Pulling $IMAGE" $ENGINE pull $IMAGE - - name: Check if PRdoc is required - id: get-labels - run: | # Fetch the labels for the PR under test echo "Fetch the labels for $API_BASE/${REPO}/pulls/${GITHUB_PR}" labels=$( curl -H "Authorization: token ${GITHUB_TOKEN}" -s "$API_BASE/${REPO}/pulls/${GITHUB_PR}" | jq '.labels | .[] | .name' | tr "\n" ",") echo "Labels: ${labels}" echo "labels=${labels}" >> "$GITHUB_OUTPUT" - - name: Checkout repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1 - - - name: Check PRDoc version - run: | + echo "Checking PRdoc version" $ENGINE run --rm -v $PWD:/repo $IMAGE --version - name: Early exit if PR is silent @@ -62,4 +56,12 @@ jobs: run: | echo "Checking for PR#${GITHUB_PR}" echo "You can find more information about PRDoc at $PRDOC_DOC" - $ENGINE run --rm -v $PWD:/repo $IMAGE check -n ${GITHUB_PR} + $ENGINE run --rm -v $PWD:/repo -e RUST_LOG=info $IMAGE check -n ${GITHUB_PR} + + - name: Validate prdoc for PR#${{ github.event.pull_request.number }} + if: ${{ !contains(steps.get-labels.outputs.labels, 'R0') }} + run: | + echo "Validating PR#${GITHUB_PR}" + python3 --version + python3 -m pip install cargo-workspace==1.2.1 + python3 .github/scripts/check-prdoc.py Cargo.toml prdoc/pr_${GITHUB_PR}.prdoc diff --git a/.github/workflows/check-workspace.yml b/.github/workflows/check-workspace.yml index 3dd812d7d9b3743062553b700adba9d6abd93c50..81ec311ccce8153d7a28f68ff801cc917c8d1fd9 100644 --- a/.github/workflows/check-workspace.yml +++ b/.github/workflows/check-workspace.yml @@ -2,8 +2,6 @@ name: Check workspace on: pull_request: - paths: - - "*.toml" merge_group: jobs: @@ -19,5 +17,5 @@ jobs: run: > python3 .github/scripts/check-workspace.py . --exclude - "substrate/frame/contracts/fixtures/build" + "substrate/frame/contracts/fixtures/build" "substrate/frame/contracts/fixtures/contracts/common" diff --git a/.github/workflows/gitspiegel-trigger.yml b/.github/workflows/gitspiegel-trigger.yml index b338f7a3f6254b9db628f8b2b45c88b8094ef390..01058ad74d0b71385a8096964ea6c779fc6f4869 100644 --- a/.github/workflows/gitspiegel-trigger.yml +++ b/.github/workflows/gitspiegel-trigger.yml @@ -13,14 +13,15 @@ on: - unlocked - ready_for_review - reopened + # doesn't work as intended, triggers "workflow_run" webhook in any case # the job doesn't check out any code, so it is relatively safe to run it on any event - pull_request_target: - types: - - opened - - synchronize - - unlocked - - ready_for_review - - reopened + # pull_request_target: + # types: + # - opened + # - synchronize + # - unlocked + # - ready_for_review + # - reopened merge_group: # drop all permissions for GITHUB_TOKEN diff --git a/.github/workflows/release-99_notif-published.yml b/.github/workflows/release-99_notif-published.yml index b35120ca4e128beaa37047b0ac3f21b02f4da663..732db15d9c0c056a9d037785bbce3c87f1bc2620 100644 --- a/.github/workflows/release-99_notif-published.yml +++ b/.github/workflows/release-99_notif-published.yml @@ -8,13 +8,11 @@ on: jobs: ping_matrix: runs-on: ubuntu-latest + environment: release strategy: matrix: channel: # Internal - - name: 'RelEng: Cumulus Release Coordination' - room: '!NAEMyPAHWOiOQHsvus:parity.io' - pre-releases: true - name: "RelEng: Polkadot Release Coordination" room: '!cqAmzdIcbOFwrdrubV:parity.io' pre-release: true @@ -31,18 +29,15 @@ jobs: pre-release: true # Public - # - name: '#KusamaValidatorLounge:polkadot.builders' - # room: '!LhjZccBOqFNYKLdmbb:polkadot.builders' - # pre-releases: false - # - name: '#kusama-announcements:matrix.parity.io' - # room: '!FMwxpQnYhRCNDRsYGI:matrix.parity.io' - # pre-release: false - # - name: '#polkadotvalidatorlounge:web3.foundation' - # room: '!NZrbtteFeqYKCUGQtr:matrix.parity.io' - # pre-release: false - # - name: '#polkadot-announcements:matrix.parity.io' - # room: '!UqHPWiCBGZWxrmYBkF:matrix.parity.io' - # pre-release: false + - name: '#polkadotvalidatorlounge:web3.foundation' + room: '!NZrbtteFeqYKCUGQtr:matrix.parity.io' + pre-releases: false + - name: '#polkadot-announcements:parity.io' + room: '!UqHPWiCBGZWxrmYBkF:matrix.parity.io' + pre-releases: false + - name: '#kusama-announce:parity.io' + room: '!FMwxpQnYhRCNDRsYGI:matrix.parity.io' + pre-releases: false steps: - name: Matrix notification to ${{ matrix.channel.name }} diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml index 0a7e80f007c5b643ce183fdca85d91c57b61f53f..5b036115b2386c366b2f1e78e9ce1dc7d526eedd 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -23,7 +23,7 @@ jobs: app_id: ${{ secrets.REVIEW_APP_ID }} private_key: ${{ secrets.REVIEW_APP_KEY }} - name: "Evaluates PR reviews and assigns reviewers" - uses: paritytech/review-bot@v2.3.0 + uses: paritytech/review-bot@v2.4.0 with: repo-token: ${{ steps.app_token.outputs.token }} team-token: ${{ steps.app_token.outputs.token }} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c90a6c8e6e27a932c421378a333321f69f90ad0a..7f8796ca51248acb8be92fcb9f76e280b18e605e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -121,9 +121,8 @@ default: before_script: - 'curl --header "PRIVATE-TOKEN: $FL_CI_GROUP_TOKEN" -o forklift -L "${CI_API_V4_URL}/projects/676/packages/generic/forklift/${FL_FORKLIFT_VERSION}/forklift_${FL_FORKLIFT_VERSION}_linux_amd64"' - chmod +x forklift - - mkdir .forklift - - cp $FL_FORKLIFT_CONFIG .forklift/config.toml - - export FORKLIFT_PACKAGE_SUFFIX=${CI_JOB_NAME/ [0-9 \/]*} + - mkdir ~/.forklift + - cp $FL_FORKLIFT_CONFIG ~/.forklift/config.toml - shopt -s expand_aliases - export PATH=$PATH:$(pwd) - | @@ -131,11 +130,8 @@ default: echo "FORKLIFT_BYPASS not set, creating alias cargo='forklift cargo'" alias cargo="forklift cargo" fi - - ls -al - - rm -f forklift.sock # - echo "FL_FORKLIFT_VERSION ${FL_FORKLIFT_VERSION}" - - echo "FORKLIFT_PACKAGE_SUFFIX $FORKLIFT_PACKAGE_SUFFIX" .common-refs: rules: diff --git a/.gitlab/check-each-crate.py b/.gitlab/check-each-crate.py index da2eaad36c522e5ebfdc0d43e78c38507807e1a6..9b654f8071ac7237fe9c7c943540e8e020cebd6e 100755 --- a/.gitlab/check-each-crate.py +++ b/.gitlab/check-each-crate.py @@ -55,7 +55,7 @@ for i in range(0, crates_per_group + overflow_crates): print(f"Checking {crates[crate][0]}", file=sys.stderr) - res = subprocess.run(["cargo", "check", "--locked"], cwd = crates[crate][1]) + res = subprocess.run(["forklift", "cargo", "check", "--locked"], cwd = crates[crate][1]) if res.returncode != 0: sys.exit(1) diff --git a/.gitlab/pipeline/build.yml b/.gitlab/pipeline/build.yml index 20aa4a5c2a2835cdb859a0768be055064594b6b3..15b4869997be186c71cecbc89060b7591d71ffa3 100644 --- a/.gitlab/pipeline/build.yml +++ b/.gitlab/pipeline/build.yml @@ -314,8 +314,9 @@ build-linux-substrate: # 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" + - !reference [.forklift-cache, before_script] script: - - WASM_BUILD_NO_COLOR=1 time cargo build --locked --release -p staging-node-cli + - time WASM_BUILD_NO_COLOR=1 cargo build --locked --release -p staging-node-cli - mv $CARGO_TARGET_DIR/release/substrate-node ./artifacts/substrate/substrate - echo -n "Substrate version = " - if [ "${CI_COMMIT_TAG}" ]; then @@ -329,6 +330,18 @@ build-linux-substrate: # - printf '\n# building node-template\n\n' # - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz +build-runtimes-polkavm: + stage: build + extends: + - .docker-env + - .common-refs + - .run-immediately + script: + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p minimal-runtime + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p westend-runtime + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p rococo-runtime + - SUBSTRATE_RUNTIME_TARGET=riscv cargo check -p polkadot-test-runtime + .build-subkey: stage: build extends: @@ -341,9 +354,10 @@ build-linux-substrate: CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" before_script: - mkdir -p ./artifacts/subkey + - !reference [.forklift-cache, before_script] script: - cd ./substrate/bin/utils/subkey - - SKIP_WASM_BUILD=1 time cargo build --locked --release + - time SKIP_WASM_BUILD=1 cargo build --locked --release # - cd - # - mv $CARGO_TARGET_DIR/release/subkey ./artifacts/subkey/. # - echo -n "Subkey version = " @@ -382,3 +396,18 @@ build-subkey-linux: # after_script: [""] # tags: # - osx + +# bridges + +# we need some non-binary artifacts in our bridges+zombienet image +prepare-bridges-zombienet-artifacts: + stage: build + extends: + - .docker-env + - .common-refs + - .run-immediately + - .collect-artifacts + before_script: + - mkdir -p ./artifacts/bridges-polkadot-sdk/bridges + script: + - cp -r bridges/testing ./artifacts/bridges-polkadot-sdk/bridges/testing diff --git a/.gitlab/pipeline/check.yml b/.gitlab/pipeline/check.yml index 1ed12e68c2ce19b67dd5aca03cec85702351c039..4d71a473372d3f0caaccfd6f791392c0d5c992f3 100644 --- a/.gitlab/pipeline/check.yml +++ b/.gitlab/pipeline/check.yml @@ -108,8 +108,10 @@ check-toml-format: export RUST_LOG=remote-ext=debug,runtime=debug echo "---------- Downloading try-runtime CLI ----------" - curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.5.0/try-runtime-x86_64-unknown-linux-musl -o try-runtime + curl -sL https://github.com/paritytech/try-runtime-cli/releases/download/v0.5.4/try-runtime-x86_64-unknown-linux-musl -o try-runtime chmod +x ./try-runtime + echo "Using try-runtime-cli version:" + ./try-runtime --version echo "---------- Building ${PACKAGE} runtime ----------" time cargo build --release --locked -p "$PACKAGE" --features try-runtime @@ -133,6 +135,7 @@ check-runtime-migration-westend: WASM: "westend_runtime.compact.compressed.wasm" URI: "wss://westend-try-runtime-node.parity-chains.parity.io:443" SUBCOMMAND_EXTRA_ARGS: "--no-weight-warnings" + allow_failure: true check-runtime-migration-rococo: stage: check diff --git a/.gitlab/pipeline/publish.yml b/.gitlab/pipeline/publish.yml index 3b77f5363f627070984be057fb105d45c290be6e..b73acb560f67f93e540826b95fcf075374189846 100644 --- a/.gitlab/pipeline/publish.yml +++ b/.gitlab/pipeline/publish.yml @@ -66,6 +66,8 @@ publish-rustdoc: # note: images are used not only in zombienet but also in rococo, wococo and versi .build-push-image: image: $BUILDAH_IMAGE + extends: + - .zombienet-refs variables: DOCKERFILE: "" # docker/path-to.Dockerfile IMAGE_NAME: "" # docker.io/paritypr/image_name @@ -77,6 +79,7 @@ publish-rustdoc: --build-arg VCS_REF="${CI_COMMIT_SHA}" --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" --build-arg IMAGE_NAME="${IMAGE_NAME}" + --build-arg ZOMBIENET_IMAGE="${ZOMBIENET_IMAGE}" --tag "$IMAGE_NAME:${DOCKER_IMAGES_VERSION}" --file ${DOCKERFILE} . - echo "$PARITYPR_PASS" | @@ -163,3 +166,22 @@ build-push-image-substrate-pr: variables: DOCKERFILE: "docker/dockerfiles/substrate_injected.Dockerfile" IMAGE_NAME: "docker.io/paritypr/substrate" + +# unlike other images, bridges+zombienet image is based on Zombienet image that pulls required binaries +# from other fresh images (polkadot and cumulus) +build-push-image-bridges-zombienet-tests: + stage: publish + extends: + - .kubernetes-env + - .common-refs + - .build-push-image + needs: + - job: build-linux-stable + artifacts: true + - job: build-linux-stable-cumulus + artifacts: true + - job: prepare-bridges-zombienet-artifacts + artifacts: true + variables: + DOCKERFILE: "docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile" + IMAGE_NAME: "docker.io/paritypr/bridges-zombienet-tests" diff --git a/.gitlab/pipeline/test.yml b/.gitlab/pipeline/test.yml index e75700ffddc468a918b216875294e362571754f5..9f774aab271938138bb78975c48e2de6caf35aa1 100644 --- a/.gitlab/pipeline/test.yml +++ b/.gitlab/pipeline/test.yml @@ -48,6 +48,7 @@ test-linux-stable: - target/nextest/default/junit.xml reports: junit: target/nextest/default/junit.xml + timeout: 90m test-linux-oldkernel-stable: extends: test-linux-stable @@ -224,6 +225,7 @@ cargo-check-benches: git merge --verbose --no-edit FETCH_HEAD; fi fi' + - !reference [.forklift-cache, before_script] parallel: 2 script: - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index a1d4db580cca734918c78bbb850cefcf3d06010e..55120e66d0e53c740b16a7ee6276230f42c172ef 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -10,3 +10,5 @@ include: - .gitlab/pipeline/zombienet/cumulus.yml # polkadot tests - .gitlab/pipeline/zombienet/polkadot.yml + # bridges tests + - .gitlab/pipeline/zombienet/bridges.yml diff --git a/.gitlab/pipeline/zombienet/bridges.yml b/.gitlab/pipeline/zombienet/bridges.yml new file mode 100644 index 0000000000000000000000000000000000000000..4278f59b1e9a2e33f32bf255436d6af5d31b30fb --- /dev/null +++ b/.gitlab/pipeline/zombienet/bridges.yml @@ -0,0 +1,63 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "zombienet" stage for bridges + +# common settings for all zombienet jobs +.zombienet-bridges-common: + extends: + - .kubernetes-env + - .zombienet-refs + rules: + # Docker images have different tag in merge queues + - if: $CI_COMMIT_REF_NAME =~ /^gh-readonly-queue.*$/ + variables: + DOCKER_IMAGES_VERSION: ${CI_COMMIT_SHORT_SHA} + - !reference [.build-refs, rules] + before_script: + - echo "Zombienet Tests Config" + - echo "${ZOMBIENET_IMAGE}" + - echo "${GH_DIR}" + - echo "${LOCAL_DIR}" + - ls "${LOCAL_DIR}" + - export DEBUG=zombie,zombie::network-node + - export ZOMBIENET_INTEGRATION_TEST_IMAGE="${BRIDGES_ZOMBIENET_TESTS_IMAGE}":${BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG} + - echo "${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + stage: zombienet + image: "${BRIDGES_ZOMBIENET_TESTS_IMAGE}:${BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG}" + needs: + - job: build-push-image-bridges-zombienet-tests + artifacts: true + variables: + BRIDGES_ZOMBIENET_TESTS_IMAGE_TAG: ${DOCKER_IMAGES_VERSION} + BRIDGES_ZOMBIENET_TESTS_IMAGE: "docker.io/paritypr/bridges-zombienet-tests" + GH_DIR: "https://github.com/paritytech/polkadot-sdk/tree/${CI_COMMIT_SHA}/bridges/testing" + LOCAL_DIR: "/builds/parity/mirrors/polkadot-sdk/bridges/testing" + FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 + RUN_IN_CONTAINER: "1" + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}_zombienet_bridge_tests" + when: always + expire_in: 2 days + paths: + - ./zombienet-logs + after_script: + - mkdir -p ./zombienet-logs + # copy general logs + - cp -r /tmp/bridges-tests-run-*/logs/* ./zombienet-logs/ + # copy logs of rococo nodes + - cp -r /tmp/bridges-tests-run-*/bridge_hub_rococo_local_network/*.log ./zombienet-logs/ + # copy logs of westend nodes + - cp -r /tmp/bridges-tests-run-*/bridge_hub_westend_local_network/*.log ./zombienet-logs/ + +zombienet-bridges-0001-asset-transfer-works: + extends: + - .zombienet-bridges-common + script: + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0001-asset-transfer --docker + - echo "Done" + +zombienet-bridges-0002-mandatory-headers-synced-while-idle: + extends: + - .zombienet-bridges-common + script: + - /home/nonroot/bridges-polkadot-sdk/bridges/testing/run-new-test.sh 0002-mandatory-headers-synced-while-idle --docker + - echo "Done" diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 7f5d424ec1b6d18c223f7404ff816646e0fc4c37..97572f029d0020f090a8fd16839028ac9f088cf9 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -150,6 +150,22 @@ zombienet-polkadot-functional-0010-validator-disabling: --local-dir="${LOCAL_DIR}/functional" --test="0010-validator-disabling.zndsl" +zombienet-polkadot-functional-0011-async-backing-6-seconds-rate: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0011-async-backing-6-seconds-rate.zndsl" + +zombienet-polkadot-functional-0012-elastic-scaling-mvp: + extends: + - .zombienet-polkadot-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0012-elastic-scaling-mvp.zndsl" + zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: - .zombienet-polkadot-common diff --git a/Cargo.lock b/Cargo.lock index 36e94ebae4eed2d1c4e8814b36ab94919da2cdde..410b45d00183aa93fe7b6182bc80ac4cebf26e09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,16 +86,16 @@ dependencies = [ [[package]] name = "aes-gcm" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" dependencies = [ "aead 0.4.3", "aes 0.7.5", "cipher 0.3.0", - "ctr 0.8.0", + "ctr 0.7.0", "ghash 0.4.4", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -109,14 +109,14 @@ dependencies = [ "cipher 0.4.4", "ctr 0.9.2", "ghash 0.5.0", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom 0.2.10", "once_cell", @@ -125,9 +125,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" dependencies = [ "cfg-if", "getrandom 0.2.10", @@ -191,7 +191,7 @@ checksum = "c0391754c09fab4eae3404d19d0d297aa1c670c1775ab51d8a5312afeca23157" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -206,7 +206,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "syn-solidity", "tiny-keccak", ] @@ -333,7 +333,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -978,6 +978,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -1049,11 +1050,11 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "pallet-asset-conversion", "pallet-assets", "pallet-balances", "pallet-collator-selection", "pallet-session", + "pallet-timestamp", "pallet-xcm", "pallet-xcm-bridge-hub-router", "parachains-common", @@ -1098,7 +1099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] @@ -1108,7 +1109,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ - "async-lock", + "async-lock 2.8.0", "async-task", "concurrent-queue", "fastrand 1.9.0", @@ -1122,7 +1123,7 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "blocking", "futures-lite", @@ -1134,7 +1135,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ - "async-lock", + "async-lock 2.8.0", "autocfg", "cfg-if", "concurrent-queue", @@ -1154,7 +1155,18 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy", + "pin-project-lite 0.2.12", ] [[package]] @@ -1176,11 +1188,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" dependencies = [ "async-io", - "async-lock", + "async-lock 2.8.0", "autocfg", "blocking", "cfg-if", - "event-listener", + "event-listener 2.5.3", "futures-lite", "rustix 0.37.23", "signal-hook", @@ -1206,7 +1218,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -1223,7 +1235,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -1405,7 +1417,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -1579,7 +1591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", - "async-lock", + "async-lock 2.8.0", "async-task", "atomic-waker", "fastrand 1.9.0", @@ -1993,6 +2005,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -2087,6 +2100,7 @@ dependencies = [ "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", + "pallet-timestamp", "pallet-utility", "parachains-common", "parachains-runtimes-test-utils", @@ -2161,6 +2175,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -2588,19 +2603,19 @@ dependencies = [ "clap_lex 0.2.4", "indexmap 1.9.3", "once_cell", - "strsim", + "strsim 0.10.0", "termcolor", "textwrap", ] [[package]] name = "clap" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", - "clap_derive 4.4.7", + "clap_derive 4.5.0", ] [[package]] @@ -2614,14 +2629,14 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.18" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", - "clap_lex 0.6.0", - "strsim", + "clap_lex 0.7.0", + "strsim 0.11.0", "terminal_size", ] @@ -2631,7 +2646,7 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "586a385f7ef2f8b4d86bddaa0c094794e7ccbfe5ffef1f434fe928143fc783a5" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", ] [[package]] @@ -2649,14 +2664,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -2670,9 +2685,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "coarsetime" @@ -2718,6 +2733,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -2972,6 +2988,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -3065,6 +3082,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -3093,7 +3111,6 @@ dependencies = [ "pallet-xcm-benchmarks", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-runtime-common", "rococo-runtime-constants", @@ -3129,6 +3146,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "frame-benchmarking", @@ -3143,11 +3161,11 @@ dependencies = [ "pallet-aura", "pallet-authorship", "pallet-balances", + "pallet-broker", "pallet-collator-selection", "pallet-message-queue", "pallet-multisig", "pallet-session", - "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", @@ -3156,7 +3174,6 @@ dependencies = [ "pallet-xcm-benchmarks", "parachains-common", "parity-scale-codec", - "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-runtime-common", "scale-info", @@ -3364,7 +3381,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.4.18", + "clap 4.5.1", "criterion-plot", "futures", "is-terminal", @@ -3460,7 +3477,7 @@ checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -3492,24 +3509,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] name = "ctr" -version = "0.8.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" dependencies = [ "cipher 0.3.0", ] @@ -3527,7 +3544,7 @@ dependencies = [ name = "cumulus-client-cli" version = "0.7.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "parity-scale-codec", "sc-chain-spec", "sc-cli", @@ -3857,6 +3874,7 @@ dependencies = [ "pallet-message-queue", "parity-scale-codec", "polkadot-parachain-primitives", + "polkadot-runtime-common", "polkadot-runtime-parachains", "rand", "sc-client-api", @@ -3885,7 +3903,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -4030,6 +4048,25 @@ dependencies = [ "sp-trie", ] +[[package]] +name = "cumulus-primitives-storage-weight-reclaim" +version = "1.0.0" +dependencies = [ + "cumulus-primitives-core", + "cumulus-primitives-proof-size-hostfunction", + "cumulus-test-runtime", + "docify", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", + "sp-trie", +] + [[package]] name = "cumulus-primitives-timestamp" version = "0.7.0" @@ -4050,7 +4087,6 @@ dependencies = [ "frame-support", "log", "pallet-asset-conversion", - "pallet-xcm-benchmarks", "parity-scale-codec", "polkadot-runtime-common", "polkadot-runtime-parachains", @@ -4192,6 +4228,7 @@ dependencies = [ "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", + "cumulus-primitives-storage-weight-reclaim", "cumulus-test-relay-sproof-builder", "cumulus-test-runtime", "cumulus-test-service", @@ -4236,6 +4273,7 @@ version = "0.1.0" dependencies = [ "cumulus-pallet-parachain-system", "cumulus-primitives-core", + "cumulus-primitives-storage-weight-reclaim", "frame-executive", "frame-support", "frame-system", @@ -4268,7 +4306,7 @@ name = "cumulus-test-service" version = "0.1.0" dependencies = [ "async-trait", - "clap 4.4.18", + "clap 4.5.1", "criterion 0.5.1", "cumulus-client-cli", "cumulus-client-consensus-common", @@ -4278,6 +4316,7 @@ dependencies = [ "cumulus-client-service", "cumulus-pallet-parachain-system", "cumulus-primitives-core", + "cumulus-primitives-storage-weight-reclaim", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", @@ -4349,7 +4388,7 @@ dependencies = [ "byteorder", "digest 0.8.1", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -4362,15 +4401,15 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -4379,7 +4418,7 @@ dependencies = [ "fiat-crypto", "platforms", "rustc_version 0.4.0", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -4391,7 +4430,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -4431,7 +4470,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -4448,7 +4487,7 @@ checksum = "50c49547d73ba8dcfd4ad7325d64c6d5391ff4224d498fc39a6f3f49825a530d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -4603,7 +4642,7 @@ dependencies = [ "block-buffer 0.10.4", "const-oid", "crypto-common", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -4656,7 +4695,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -4717,7 +4756,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.48", + "syn 2.0.50", "termcolor", "toml 0.8.8", "walkdir", @@ -4804,12 +4843,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f628eaec48bfd21b865dc2950cfa014450c01d2fa2b69a86c2fd5844ec523c0" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "ed25519", "rand_core 0.6.4", "serde", "sha2 0.10.7", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -4833,7 +4872,7 @@ version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "ed25519", "hashbrown 0.14.3", "hex", @@ -4863,7 +4902,7 @@ dependencies = [ "pkcs8", "rand_core 0.6.4", "sec1", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -4888,10 +4927,10 @@ dependencies = [ "paste", "polkadot-primitives", "polkadot-runtime-parachains", - "polkadot-service", "sc-consensus-grandpa", "sp-authority-discovery", "sp-consensus-babe", + "sp-consensus-beefy", "sp-core", "sp-runtime", "staging-xcm", @@ -4942,7 +4981,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -4953,7 +4992,7 @@ checksum = "c2ad8cef1d801a4686bfd8919f0b30eac4c8e48968c437a6405ded4fb5272d2b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -5091,6 +5130,27 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite 0.2.12", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite 0.2.12", +] + [[package]] name = "exit-future" version = "0.2.0" @@ -5122,7 +5182,7 @@ dependencies = [ "fs-err", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -5237,7 +5297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -5399,7 +5459,6 @@ dependencies = [ "pallet-examples", "parity-scale-codec", "scale-info", - "simple-mermaid", "sp-api", "sp-arithmetic", "sp-block-builder", @@ -5450,7 +5509,7 @@ dependencies = [ "Inflector", "array-bytes 6.1.0", "chrono", - "clap 4.4.18", + "clap 4.5.1", "comfy-table", "frame-benchmarking", "frame-support", @@ -5516,7 +5575,7 @@ dependencies = [ "quote", "scale-info", "sp-arithmetic", - "syn 2.0.48", + "syn 2.0.50", "trybuild", ] @@ -5542,7 +5601,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -5671,7 +5730,7 @@ dependencies = [ "quote", "regex", "sp-crypto-hashing", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -5682,7 +5741,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -5691,7 +5750,7 @@ version = "11.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -5854,9 +5913,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -5885,9 +5944,9 @@ checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -5924,7 +5983,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -6151,6 +6210,24 @@ dependencies = [ "testnet-parachains-constants", ] +[[package]] +name = "governor" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot 0.12.1", + "quanta", + "rand", + "smallvec", +] + [[package]] name = "group" version = "0.13.0" @@ -6159,7 +6236,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -6174,7 +6251,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.0.0", + "indexmap 2.2.3", "slab", "tokio", "tokio-util", @@ -6222,7 +6299,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.8", ] [[package]] @@ -6231,7 +6308,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", ] [[package]] @@ -6240,7 +6317,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "allocator-api2", "serde", ] @@ -6312,7 +6389,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac 0.11.0", "digest 0.9.0", ] @@ -6440,9 +6517,9 @@ dependencies = [ "hyper", "log", "rustls 0.21.6", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", ] [[package]] @@ -6605,9 +6682,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -6621,9 +6698,9 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "indicatif" -version = "0.17.6" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" dependencies = [ "console", "instant", @@ -6770,9 +6847,9 @@ checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" [[package]] name = "jsonrpsee" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affdc52f7596ccb2d7645231fc6163bb314630c989b64998f3699a28b4d5d4dc" +checksum = "4a95f7cc23d5fab0cdeeaf6bad8c8f5e7a3aa7f0d211957ea78232b327ab27b0" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-client", @@ -6786,19 +6863,20 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935" +checksum = "6b1736cfa3845fd9f8f43751f2b8e0e83f7b6081e754502f7d63b6587692cc83" dependencies = [ "futures-util", "http", "jsonrpsee-core", "pin-project", - "rustls-native-certs", + "rustls-native-certs 0.7.0", + "rustls-pki-types", "soketto", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.25.0", "tokio-util", "tracing", "url", @@ -6806,12 +6884,12 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b" +checksum = "82030d038658974732103e623ba2e0abec03bbbe175b39c0a2fafbada60c5868" dependencies = [ "anyhow", - "async-lock", + "async-lock 3.3.0", "async-trait", "beef", "futures-timer", @@ -6819,21 +6897,22 @@ dependencies = [ "hyper", "jsonrpsee-types", "parking_lot 0.12.1", + "pin-project", "rand", "rustc-hash", "serde", "serde_json", - "soketto", "thiserror", "tokio", + "tokio-stream", "tracing", ] [[package]] name = "jsonrpsee-http-client" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20" +checksum = "36a06ef0de060005fddf772d54597bb6a8b0413da47dcffd304b0306147b9678" dependencies = [ "async-trait", "hyper", @@ -6851,12 +6930,12 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29110019693a4fa2dbda04876499d098fa16d70eba06b1e6e2b3f1b251419515" +checksum = "69fc56131589f82e57805f7338b87023db4aafef813555708b159787e34ad6bc" dependencies = [ "heck", - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.0.0", "proc-macro2", "quote", "syn 1.0.109", @@ -6864,15 +6943,16 @@ dependencies = [ [[package]] name = "jsonrpsee-server" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c39a00449c9ef3f50b84fc00fc4acba20ef8f559f07902244abf4c15c5ab9c" +checksum = "d85be77fe5b2a94589e3164fb780017f7aff7d646b49278c0d0346af16975c8e" dependencies = [ "futures-util", "http", "hyper", "jsonrpsee-core", "jsonrpsee-types", + "pin-project", "route-recognizer", "serde", "serde_json", @@ -6887,23 +6967,22 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be0be325642e850ed0bdff426674d2e66b2b7117c9be23a7caef68a2902b7d9" +checksum = "9a48fdc1202eafc51c63e00406575e59493284ace8b8b61aa16f3a6db5d64f1a" dependencies = [ "anyhow", "beef", "serde", "serde_json", "thiserror", - "tracing", ] [[package]] name = "jsonrpsee-ws-client" -version = "0.20.3" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0" +checksum = "c5ce25d70a8e4d3cc574bbc3cad0137c326ad64b194793d5e7bbdd3fa4504181" dependencies = [ "http", "jsonrpsee-client-transport", @@ -7013,6 +7092,7 @@ dependencies = [ "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", + "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", @@ -7631,7 +7711,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -7826,6 +7906,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +dependencies = [ + "libc", +] + [[package]] name = "macro_magic" version = "0.5.0" @@ -7835,7 +7924,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -7849,7 +7938,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -7860,7 +7949,7 @@ checksum = "9ea73aa640dc01d62a590d48c0c3521ed739d53b27f919b25c3551e233481654" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -7871,7 +7960,7 @@ checksum = "ef9d79ae96aaba821963320eb2b6e34d17df1e5a83d8a1985c29cc5be59577b3" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -8040,7 +8129,7 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "minimal-node" version = "4.0.0-dev" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "frame", "futures", "futures-timer", @@ -8119,7 +8208,7 @@ dependencies = [ "bitflags 1.3.2", "blake2 0.10.6", "c2-chacha", - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "either", "hashlink", "lioness", @@ -8128,7 +8217,7 @@ dependencies = [ "rand", "rand_chacha 0.3.1", "rand_distr", - "subtle 2.4.1", + "subtle 2.5.0", "thiserror", "zeroize", ] @@ -8492,6 +8581,12 @@ dependencies = [ "libc", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "no-std-net" version = "0.6.0" @@ -8503,7 +8598,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes 6.1.0", - "clap 4.4.18", + "clap 4.5.1", "derive_more", "fs_extra", "futures", @@ -8580,7 +8675,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "generate-bags", "kitchensink-runtime", ] @@ -8589,7 +8684,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -8633,7 +8728,7 @@ dependencies = [ name = "node-template-release" version = "3.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "flate2", "fs_extra", "glob", @@ -8742,6 +8837,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -8893,9 +8994,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -8951,7 +9052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eedb646674596266dc9bb2b5c7eea7c36b32ecc7777eba0d510196972d72c4fd" dependencies = [ "expander 2.0.0", - "indexmap 2.0.0", + "indexmap 2.2.3", "itertools 0.11.0", "petgraph", "proc-macro-crate 1.3.1", @@ -9564,7 +9665,7 @@ dependencies = [ "anyhow", "frame-system", "parity-wasm", - "polkavm-linker", + "polkavm-linker 0.5.0", "sp-runtime", "tempfile", "toml 0.8.8", @@ -9616,7 +9717,7 @@ version = "18.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -9626,7 +9727,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", - "polkavm-derive", + "polkavm-derive 0.5.0", "scale-info", ] @@ -9728,6 +9829,7 @@ dependencies = [ "pallet-bags-list", "pallet-balances", "pallet-election-provider-multi-phase", + "pallet-nomination-pools", "pallet-session", "pallet-staking", "pallet-timestamp", @@ -10422,6 +10524,26 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-parameters" +version = "0.0.1" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-example-basic", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", +] + [[package]] name = "pallet-preimage" version = "28.0.0" @@ -10770,7 +10892,7 @@ dependencies = [ "proc-macro2", "quote", "sp-runtime", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -11176,7 +11298,7 @@ dependencies = [ name = "parachain-template-node" version = "0.1.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "color-print", "cumulus-client-cli", "cumulus-client-collator", @@ -11240,6 +11362,7 @@ dependencies = [ "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-primitives-core", + "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", "frame-benchmarking", "frame-executive", @@ -11332,6 +11455,7 @@ dependencies = [ "pallet-balances", "pallet-collator-selection", "pallet-session", + "pallet-timestamp", "pallet-xcm", "parity-scale-codec", "polkadot-parachain-primitives", @@ -11513,7 +11637,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac 0.11.0", ] [[package]] @@ -11660,6 +11784,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "enumflags2", @@ -11758,6 +11883,7 @@ dependencies = [ "cumulus-pallet-session-benchmarking", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", + "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-utility", "enumflags2", @@ -11848,7 +11974,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -11869,7 +11995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.0.0", + "indexmap 2.2.3", ] [[package]] @@ -11889,7 +12015,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -12102,7 +12228,7 @@ name = "polkadot-cli" version = "7.0.0" dependencies = [ "cfg-if", - "clap 4.4.18", + "clap 4.5.1", "frame-benchmarking-cli", "futures", "log", @@ -12121,6 +12247,7 @@ dependencies = [ "sp-io", "sp-keyring", "sp-maybe-compressed-blob", + "sp-runtime", "substrate-build-script-utils", "thiserror", "try-runtime-cli", @@ -12178,7 +12305,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 2.0.0", + "indexmap 2.2.3", "lazy_static", "parity-scale-codec", "polkadot-erasure-coding", @@ -12223,6 +12350,7 @@ dependencies = [ "futures", "futures-timer", "lazy_static", + "parking_lot 0.12.1", "polkadot-node-network-protocol", "polkadot-node-subsystem", "polkadot-node-subsystem-test-helpers", @@ -12384,7 +12512,9 @@ dependencies = [ "polkadot-primitives", "polkadot-primitives-test-helpers", "polkadot-statement-table", + "rstest", "sc-keystore", + "schnellru", "sp-application-crypto", "sp-core", "sp-keyring", @@ -12945,7 +13075,7 @@ dependencies = [ "async-trait", "bridge-hub-rococo-runtime", "bridge-hub-westend-runtime", - "clap 4.4.18", + "clap 4.5.1", "collectives-westend-runtime", "color-print", "contracts-rococo-runtime", @@ -13054,6 +13184,7 @@ version = "7.0.0" dependencies = [ "bitvec", "hex-literal", + "log", "parity-scale-codec", "polkadot-core-primitives", "polkadot-parachain-primitives", @@ -13146,7 +13277,6 @@ dependencies = [ "pallet-transaction-payment", "pallet-treasury", "pallet-vesting", - "pallet-xcm-benchmarks", "parity-scale-codec", "polkadot-primitives", "polkadot-primitives-test-helpers", @@ -13220,6 +13350,7 @@ dependencies = [ "polkadot-runtime-metrics", "rand", "rand_chacha 0.3.1", + "rstest", "rustc-hex", "sc-keystore", "scale-info", @@ -13280,6 +13411,7 @@ dependencies = [ "staging-chain-spec-builder", "staging-node-cli", "staging-parachain-info", + "staging-xcm", "subkey", "substrate-wasm-builder", ] @@ -13420,7 +13552,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 2.0.0", + "indexmap 2.2.3", "parity-scale-codec", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -13450,6 +13582,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "sp-core", + "tracing-gum", ] [[package]] @@ -13458,24 +13591,28 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", + "bincode", "bitvec", - "clap 4.4.18", + "clap 4.5.1", "clap-num", "color-eyre", "colored", "env_logger 0.9.3", "futures", "futures-timer", + "hex", "itertools 0.11.0", "kvdb-memorydb", "log", "orchestra", "parity-scale-codec", "paste", + "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-erasure-coding", + "polkadot-node-core-approval-voting", "polkadot-node-core-av-store", "polkadot-node-core-chain-api", "polkadot-node-metrics", @@ -13492,17 +13629,24 @@ dependencies = [ "pyroscope", "pyroscope_pprofrs", "rand", + "rand_chacha 0.3.1", + "rand_core 0.6.4", "rand_distr", "sc-keystore", "sc-network", "sc-service", + "schnorrkel 0.9.1", "serde", "serde_yaml", + "sha1", "sp-application-crypto", "sp-consensus", + "sp-consensus-babe", "sp-core", "sp-keyring", "sp-keystore", + "sp-runtime", + "sp-timestamp", "substrate-prometheus-endpoint", "tokio", "tracing-gum", @@ -13543,7 +13687,7 @@ version = "1.0.0" dependencies = [ "assert_matches", "async-trait", - "clap 4.4.18", + "clap 4.5.1", "color-eyre", "futures", "futures-timer", @@ -13690,7 +13834,7 @@ dependencies = [ name = "polkadot-voter-bags" version = "7.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "generate-bags", "sp-io", "westend-runtime", @@ -13702,14 +13846,29 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b4e215c80fe876147f3d58158d5dfeae7dabdd6047e175af77095b78d0035c" +[[package]] +name = "polkavm-common" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" + [[package]] name = "polkavm-derive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6380dbe1fb03ecc74ad55d841cfc75480222d153ba69ddcb00977866cbdabdb8" dependencies = [ - "polkavm-derive-impl", - "syn 2.0.48", + "polkavm-derive-impl 0.5.0", + "syn 2.0.50", +] + +[[package]] +name = "polkavm-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" +dependencies = [ + "polkavm-derive-impl-macro", ] [[package]] @@ -13718,10 +13877,32 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc8211b3365bbafb2fb32057d68b0e1ca55d079f5cf6f9da9b98079b94b3987d" dependencies = [ - "polkavm-common", + "polkavm-common 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.50", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" +dependencies = [ + "polkavm-common 0.8.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" +dependencies = [ + "polkavm-derive-impl 0.8.0", + "syn 2.0.50", ] [[package]] @@ -13734,7 +13915,22 @@ dependencies = [ "hashbrown 0.14.3", "log", "object 0.32.2", - "polkavm-common", + "polkavm-common 0.5.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + +[[package]] +name = "polkavm-linker" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdec1451cb18261d5d01de82acc15305e417fb59588cdcb3127d3dcc9672b925" +dependencies = [ + "gimli 0.28.0", + "hashbrown 0.14.3", + "log", + "object 0.32.2", + "polkavm-common 0.8.0", "regalloc2 0.9.3", "rustc-demangle", ] @@ -13763,7 +13959,7 @@ checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", "opaque-debug 0.3.0", - "universal-hash 0.4.1", + "universal-hash 0.4.0", ] [[package]] @@ -13786,7 +13982,7 @@ dependencies = [ "cfg-if", "cpufeatures", "opaque-debug 0.3.0", - "universal-hash 0.4.1", + "universal-hash 0.4.0", ] [[package]] @@ -13911,7 +14107,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -14002,7 +14198,7 @@ checksum = "9b698b0b09d40e9b7c1a47b132d66a8b54bcd20583d9b6d06e4535e383b4405c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -14074,7 +14270,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -14174,7 +14370,7 @@ dependencies = [ "itertools 0.11.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -14225,6 +14421,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "quanta" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +dependencies = [ + "crossbeam-utils", + "libc", + "mach2", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -14385,6 +14597,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -14492,7 +14713,7 @@ checksum = "7f7473c2cfcf90008193dd0e3e16599455cb601a9fce322b5bb55de799664925" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -14570,11 +14791,17 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + [[package]] name = "remote-ext-tests-bags-list" version = "1.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "frame-system", "log", "pallet-bags-list-remote-tests", @@ -14609,12 +14836,12 @@ dependencies = [ "percent-encoding", "pin-project-lite 0.2.12", "rustls 0.21.6", - "rustls-pemfile", + "rustls-pemfile 1.0.3", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", @@ -14641,7 +14868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac 0.12.1", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -14952,6 +15179,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version 0.4.0", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version 0.4.0", + "syn 2.0.50", + "unicode-ident", +] + [[package]] name = "rtnetlink" version = "0.10.1" @@ -15122,10 +15378,24 @@ checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" dependencies = [ "log", "ring 0.16.20", - "rustls-webpki", + "rustls-webpki 0.101.4", "sct", ] +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring 0.17.7", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle 2.5.0", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -15133,7 +15403,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.3", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.0.0", + "rustls-pki-types", "schannel", "security-framework", ] @@ -15147,6 +15430,22 @@ dependencies = [ "base64 0.21.2", ] +[[package]] +name = "rustls-pemfile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +dependencies = [ + "base64 0.21.2", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a716eb65e3158e90e17cd93d855216e27bde02745ab842f2cab4a39dba1bacf" + [[package]] name = "rustls-webpki" version = "0.101.4" @@ -15157,6 +15456,17 @@ dependencies = [ "untrusted 0.7.1", ] +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring 0.17.7", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -15348,7 +15658,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -15358,7 +15668,7 @@ dependencies = [ "array-bytes 6.1.0", "bip39", "chrono", - "clap 4.4.18", + "clap 4.5.1", "fdlimit", "futures", "futures-timer", @@ -15669,7 +15979,7 @@ dependencies = [ name = "sc-consensus-grandpa" version = "0.19.0" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "array-bytes 6.1.0", "assert_matches", "async-trait", @@ -16055,7 +16365,7 @@ dependencies = [ name = "sc-network-gossip" version = "0.34.0" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "async-trait", "futures", "futures-timer", @@ -16314,9 +16624,13 @@ dependencies = [ name = "sc-rpc-server" version = "11.0.0" dependencies = [ + "futures", + "governor", "http", + "hyper", "jsonrpsee", "log", + "pin-project", "serde_json", "substrate-prometheus-endpoint", "tokio", @@ -16338,11 +16652,13 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "pretty_assertions", + "rand", "sc-block-builder", "sc-chain-spec", "sc-client-api", "sc-rpc", "sc-service", + "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "serde", @@ -16358,6 +16674,7 @@ dependencies = [ "sp-version", "substrate-test-runtime", "substrate-test-runtime-client", + "substrate-test-runtime-transaction-pool", "thiserror", "tokio", "tokio-stream", @@ -16412,6 +16729,7 @@ dependencies = [ "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", + "schnellru", "serde", "serde_json", "sp-api", @@ -16509,7 +16827,7 @@ dependencies = [ name = "sc-storage-monitor" version = "0.16.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "fs4", "log", "sp-core", @@ -16611,7 +16929,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -16744,7 +17062,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "cfg-if", "hashbrown 0.13.2", ] @@ -16761,7 +17079,7 @@ dependencies = [ "merlin 2.0.1", "rand_core 0.5.1", "sha2 0.8.2", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -16790,13 +17108,13 @@ dependencies = [ "aead 0.5.2", "arrayref", "arrayvec 0.7.4", - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "getrandom_or_panic", "merlin 3.0.0", "rand_core 0.6.4", "serde_bytes", "sha2 0.10.7", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -16838,7 +17156,7 @@ dependencies = [ "der", "generic-array 0.14.7", "pkcs8", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -16995,9 +17313,9 @@ checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" [[package]] name = "serde" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -17022,13 +17340,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -17053,9 +17371,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -17085,11 +17403,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.30" +version = "0.9.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.3", "itoa", "ryu", "serde", @@ -17118,7 +17436,7 @@ checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -17147,9 +17465,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -17298,8 +17616,9 @@ dependencies = [ [[package]] name = "simple-mermaid" -version = "0.1.0" -source = "git+https://github.com/kianenigma/simple-mermaid.git?rev=e48b187bcfd5cc75111acd9d241f1bd36604344b#e48b187bcfd5cc75111acd9d241f1bd36604344b" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" [[package]] name = "siphasher" @@ -17358,7 +17677,7 @@ dependencies = [ "async-executor", "async-fs", "async-io", - "async-lock", + "async-lock 2.8.0", "async-net", "async-process", "blocking", @@ -17381,7 +17700,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0bb30cf57b7b5f6109ce17c3164445e2d6f270af2cb48f6e4d31c2967c9a9f5" dependencies = [ "arrayvec 0.7.4", - "async-lock", + "async-lock 2.8.0", "atomic-take", "base64 0.21.2", "bip39", @@ -17392,7 +17711,7 @@ dependencies = [ "derive_more", "ed25519-zebra 4.0.3", "either", - "event-listener", + "event-listener 2.5.3", "fnv", "futures-lite", "futures-util", @@ -17435,12 +17754,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "256b5bad1d6b49045e95fe87492ce73d5af81545d8b4d8318a872d2007024c33" dependencies = [ "async-channel", - "async-lock", + "async-lock 2.8.0", "base64 0.21.2", "blake2-rfc", "derive_more", "either", - "event-listener", + "event-listener 2.5.3", "fnv", "futures-channel", "futures-lite", @@ -17476,15 +17795,15 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9d1425eb528a21de2755c75af4c9b5d57f50a0d4c3b7f1828a4cd03f8ba155" dependencies = [ - "aes-gcm 0.9.4", + "aes-gcm 0.9.2", "blake2 0.10.6", "chacha20poly1305", - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "rand_core 0.6.4", "ring 0.16.20", "rustc_version 0.4.0", "sha2 0.10.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -17936,6 +18255,7 @@ dependencies = [ "sp-externalities 0.25.0", "sp-metadata-ir", "sp-runtime", + "sp-runtime-interface 24.0.0", "sp-state-machine", "sp-std 14.0.0", "sp-test-primitives", @@ -17955,7 +18275,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -18152,6 +18472,7 @@ dependencies = [ "sp-core", "sp-crypto-hashing", "sp-io", + "sp-keystore", "sp-mmr-primitives", "sp-runtime", "sp-std 14.0.0", @@ -18347,7 +18668,7 @@ version = "0.0.0" dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -18365,7 +18686,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf5 dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -18374,7 +18695,7 @@ version = "14.0.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -18465,7 +18786,6 @@ dependencies = [ "rand_chacha 0.2.2", "sp-core", "sp-externalities 0.25.0", - "thiserror", ] [[package]] @@ -18534,7 +18854,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "honggfuzz", "rand", "sp-npos-elections", @@ -18623,6 +18943,7 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", + "polkavm-derive 0.8.0", "primitive-types", "rustversion", "sp-core", @@ -18648,7 +18969,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -18660,7 +18981,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -18757,7 +19078,7 @@ name = "sp-statement-store" version = "10.0.0" dependencies = [ "aes-gcm 0.10.3", - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "ed25519-dalek", "hkdf", "parity-scale-codec", @@ -18884,7 +19205,7 @@ dependencies = [ name = "sp-trie" version = "29.0.0" dependencies = [ - "ahash 0.8.7", + "ahash 0.8.8", "array-bytes 6.1.0", "criterion 0.4.0", "hash-db", @@ -18932,7 +19253,7 @@ dependencies = [ "proc-macro2", "quote", "sp-version", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -19056,7 +19377,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" name = "staging-chain-spec-builder" version = "2.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "log", "sc-chain-spec", "serde_json", @@ -19069,7 +19390,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes 6.1.0", "assert_cmd", - "clap 4.4.18", + "clap 4.5.1", "clap_complete", "criterion 0.4.0", "frame-benchmarking", @@ -19179,7 +19500,7 @@ dependencies = [ name = "staging-node-inspect" version = "0.12.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -19323,7 +19644,7 @@ dependencies = [ "bitflags 1.3.2", "byteorder", "keccak", - "subtle 2.4.1", + "subtle 2.5.0", "zeroize", ] @@ -19333,6 +19654,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "strum" version = "0.24.1" @@ -19371,14 +19698,14 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] name = "subkey" version = "9.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "sc-cli", ] @@ -19420,7 +19747,7 @@ dependencies = [ name = "substrate-frame-cli" version = "32.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "frame-support", "frame-system", "sc-cli", @@ -19638,6 +19965,7 @@ dependencies = [ "console", "filetime", "parity-wasm", + "polkavm-linker 0.8.2", "sp-maybe-compressed-blob", "strum 0.24.1", "tempfile", @@ -19654,9 +19982,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "subtle-ng" @@ -19768,9 +20096,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -19786,7 +20114,7 @@ dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -19900,7 +20228,7 @@ dependencies = [ name = "test-parachain-adder-collator" version = "1.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "futures", "futures-timer", "log", @@ -19948,7 +20276,7 @@ dependencies = [ name = "test-parachain-undying-collator" version = "1.0.0" dependencies = [ - "clap 4.4.18", + "clap 4.5.1", "futures", "futures-timer", "log", @@ -19998,6 +20326,7 @@ dependencies = [ name = "testnet-parachains-constants" version = "1.0.0" dependencies = [ + "cumulus-primitives-core", "frame-support", "polkadot-core-primitives", "rococo-runtime-constants", @@ -20050,7 +20379,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -20211,7 +20540,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -20235,6 +20564,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.2", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -20323,7 +20663,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.3", "toml_datetime", "winnow", ] @@ -20334,7 +20674,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.2.3", "serde", "serde_spanned", "toml_datetime", @@ -20407,7 +20747,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -20449,7 +20789,7 @@ dependencies = [ "proc-macro-crate 3.0.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -20631,7 +20971,7 @@ version = "0.38.0" dependencies = [ "assert_cmd", "async-trait", - "clap 4.4.18", + "clap 4.5.1", "frame-remote-externalities", "frame-try-runtime", "hex", @@ -20784,12 +21124,12 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" dependencies = [ "generic-array 0.14.7", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -20799,7 +21139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", - "subtle 2.4.1", + "subtle 2.5.0", ] [[package]] @@ -21014,7 +21354,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "wasm-bindgen-shared", ] @@ -21048,7 +21388,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -21158,9 +21498,9 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.31.0" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f341edb80021141d4ae6468cbeefc50798716a347d4085c3811900049ea8945" +checksum = "77a8281d1d660cdf54c76a3efa9ddd0c270cada1383a995db3ccb43d166456c7" dependencies = [ "smallvec", "spin 0.9.8", @@ -21171,9 +21511,9 @@ dependencies = [ [[package]] name = "wasmi_arena" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401c1f35e413fac1846d4843745589d9ec678977ab35a384db8ae7830525d468" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" [[package]] name = "wasmi_core" @@ -21967,7 +22307,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" dependencies = [ - "curve25519-dalek 4.1.1", + "curve25519-dalek 4.1.2", "rand_core 0.6.4", "serde", "zeroize", @@ -22043,10 +22383,12 @@ dependencies = [ "pallet-transaction-payment", "pallet-xcm", "parity-scale-codec", + "polkadot-service", "polkadot-test-client", "polkadot-test-runtime", "polkadot-test-service", "sp-consensus", + "sp-core", "sp-keyring", "sp-runtime", "sp-state-machine", @@ -22063,7 +22405,7 @@ dependencies = [ "proc-macro2", "quote", "staging-xcm", - "syn 2.0.48", + "syn 2.0.50", "trybuild", ] @@ -22116,8 +22458,10 @@ name = "xcm-simulator-fuzzer" version = "1.0.0" dependencies = [ "arbitrary", + "frame-executive", "frame-support", "frame-system", + "frame-try-runtime", "honggfuzz", "pallet-balances", "pallet-message-queue", @@ -22183,7 +22527,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] @@ -22203,7 +22547,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.50", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a08c64a403136ee9bdc7be3fe0ebcba890717743..1d27cfe95392b32a4d867595ace4733beeb2ff69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,21 +36,21 @@ members = [ "bridges/primitives/test-utils", "bridges/primitives/xcm-bridge-hub", "bridges/primitives/xcm-bridge-hub-router", - "bridges/snowbridge/parachain/pallets/ethereum-client", - "bridges/snowbridge/parachain/pallets/ethereum-client/fixtures", - "bridges/snowbridge/parachain/pallets/inbound-queue", - "bridges/snowbridge/parachain/pallets/inbound-queue/fixtures", - "bridges/snowbridge/parachain/pallets/outbound-queue", - "bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree", - "bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api", - "bridges/snowbridge/parachain/pallets/system", - "bridges/snowbridge/parachain/pallets/system/runtime-api", - "bridges/snowbridge/parachain/primitives/beacon", - "bridges/snowbridge/parachain/primitives/core", - "bridges/snowbridge/parachain/primitives/ethereum", - "bridges/snowbridge/parachain/primitives/router", - "bridges/snowbridge/parachain/runtime/runtime-common", - "bridges/snowbridge/parachain/runtime/test-common", + "bridges/snowbridge/pallets/ethereum-client", + "bridges/snowbridge/pallets/ethereum-client/fixtures", + "bridges/snowbridge/pallets/inbound-queue", + "bridges/snowbridge/pallets/inbound-queue/fixtures", + "bridges/snowbridge/pallets/outbound-queue", + "bridges/snowbridge/pallets/outbound-queue/merkle-tree", + "bridges/snowbridge/pallets/outbound-queue/runtime-api", + "bridges/snowbridge/pallets/system", + "bridges/snowbridge/pallets/system/runtime-api", + "bridges/snowbridge/primitives/beacon", + "bridges/snowbridge/primitives/core", + "bridges/snowbridge/primitives/ethereum", + "bridges/snowbridge/primitives/router", + "bridges/snowbridge/runtime/runtime-common", + "bridges/snowbridge/runtime/test-common", "cumulus/client/cli", "cumulus/client/collator", "cumulus/client/consensus/aura", @@ -127,6 +127,7 @@ members = [ "cumulus/primitives/core", "cumulus/primitives/parachain-inherent", "cumulus/primitives/proof-size-hostfunction", + "cumulus/primitives/storage-weight-reclaim", "cumulus/primitives/timestamp", "cumulus/primitives/utility", "cumulus/test/client", @@ -366,6 +367,7 @@ members = [ "substrate/frame/offences/benchmarking", "substrate/frame/paged-list", "substrate/frame/paged-list/fuzzer", + "substrate/frame/parameters", "substrate/frame/preimage", "substrate/frame/proxy", "substrate/frame/ranked-collective", @@ -532,6 +534,19 @@ stable_sort_primitive = { level = "allow", priority = 2 } # prefer st extra-unused-type-parameters = { level = "allow", priority = 2 } # stylistic default_constructed_unit_structs = { level = "allow", priority = 2 } # stylistic +[workspace.dependencies] +polkavm-linker = "0.8.2" +polkavm-derive = "0.8.0" +log = { version = "0.4.20", default-features = false } +quote = { version = "1.0.33" } +serde = { version = "1.0.197", default-features = false } +serde-big-array = { version = "0.3.2" } +serde_derive = { version = "1.0.117" } +serde_json = { version = "1.0.114", default-features = false } +serde_yaml = { version = "0.9" } +syn = { version = "2.0.50" } +thiserror = { version = "1.0.48" } + [profile.release] # Polkadot runtime requires unwinding. panic = "unwind" @@ -591,6 +606,7 @@ num-bigint = { opt-level = 3 } parking_lot = { opt-level = 3 } parking_lot_core = { opt-level = 3 } percent-encoding = { opt-level = 3 } +polkavm-linker = { opt-level = 3 } primitive-types = { opt-level = 3 } reed-solomon-novelpoly = { opt-level = 3 } ring = { opt-level = 3 } diff --git a/README.md b/README.md index 93f9539a94a1de5771f04106b11ba18f27010aad..63743a456f4c8f8561bbeee8c59d63b88d352285 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,9 @@ way. The Polkadot SDK comprises three main pieces of software: [![Polkadot-license](https://img.shields.io/badge/License-GPL3-blue)](./polkadot/LICENSE) Implementation of a node for the https://polkadot.network in Rust, using the Substrate framework. This directory -currently contains runtimes for the Polkadot, Kusama, Westend, and Rococo networks. In the future, these will be -relocated to the [`runtimes`](https://github.com/polkadot-fellows/runtimes/) repository. +currently contains runtimes for the Westend and Rococo test networks. Polkadot, Kusama and their system chain runtimes +are located in the [`runtimes`](https://github.com/polkadot-fellows/runtimes/) repository maintained by +[the Polkadot Technical Fellowship](https://polkadot-fellows.github.io/dashboard/#/overview). ## [Substrate](./substrate/) [![SubstrateRustDocs](https://img.shields.io/badge/Rust_Docs-Substrate-24CC85?logo=rust)](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/substrate/index.html) diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index c0072064507331848beaaec3a56a8324296dd1f4..fac88b20ca57901d4116e147bd9363a41ff35e36 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } hash-db = { version = "0.16.0", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } static_assertions = { version = "1.1", optional = true } diff --git a/bridges/bin/runtime-common/src/refund_relayer_extension.rs b/bridges/bin/runtime-common/src/refund_relayer_extension.rs index 27b7ff1a5519b70a35c304b96b0c25108155aa46..bfcb82ad166c3a2a60891c1e18f2ad22e085cb1b 100644 --- a/bridges/bin/runtime-common/src/refund_relayer_extension.rs +++ b/bridges/bin/runtime-common/src/refund_relayer_extension.rs @@ -195,6 +195,19 @@ impl CallInfo { } } + /// Returns mutable reference to pre-dispatch `finality_target` sent to the + /// `SubmitFinalityProof` call. + #[cfg(test)] + fn submit_finality_proof_info_mut( + &mut self, + ) -> Option<&mut SubmitFinalityProofInfo> { + match *self { + Self::AllFinalityAndMsgs(ref mut info, _, _) => Some(info), + Self::RelayFinalityAndMsgs(ref mut info, _) => Some(info), + _ => None, + } + } + /// Returns the pre-dispatch `SubmitParachainHeadsInfo`. fn submit_parachain_heads_info(&self) -> Option<&SubmitParachainHeadsInfo> { match self { @@ -844,7 +857,7 @@ mod tests { use bp_parachains::{BestParaHeadHash, ParaInfo}; use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId}; use bp_runtime::{BasicOperatingMode, HeaderId}; - use bp_test_utils::{make_default_justification, test_keyring}; + use bp_test_utils::{make_default_justification, test_keyring, TEST_GRANDPA_SET_ID}; use frame_support::{ assert_storage_noop, parameter_types, traits::{fungible::Mutate, ReservableCurrency}, @@ -929,7 +942,7 @@ mod tests { let authorities = test_keyring().into_iter().map(|(a, w)| (a.into(), w)).collect(); let best_relay_header = HeaderId(best_relay_header_number, RelayBlockHash::default()); pallet_bridge_grandpa::CurrentAuthoritySet::::put( - StoredAuthoritySet::try_new(authorities, 0).unwrap(), + StoredAuthoritySet::try_new(authorities, TEST_GRANDPA_SET_ID).unwrap(), ); pallet_bridge_grandpa::BestFinalized::::put(best_relay_header); @@ -977,6 +990,23 @@ mod tests { }) } + fn submit_relay_header_call_ex(relay_header_number: RelayBlockNumber) -> RuntimeCall { + let relay_header = BridgedChainHeader::new( + relay_header_number, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); + let relay_justification = make_default_justification(&relay_header); + + RuntimeCall::BridgeGrandpa(GrandpaCall::submit_finality_proof_ex { + finality_target: Box::new(relay_header), + justification: relay_justification, + current_set_id: TEST_GRANDPA_SET_ID, + }) + } + fn submit_parachain_head_call( parachain_head_at_relay_header_number: RelayBlockNumber, ) -> RuntimeCall { @@ -1059,6 +1089,18 @@ mod tests { }) } + fn relay_finality_and_delivery_batch_call_ex( + relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + message_delivery_call(best_message), + ], + }) + } + fn relay_finality_and_confirmation_batch_call( relay_header_number: RelayBlockNumber, best_message: MessageNonce, @@ -1071,6 +1113,18 @@ mod tests { }) } + fn relay_finality_and_confirmation_batch_call_ex( + relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + message_confirmation_call(best_message), + ], + }) + } + fn all_finality_and_delivery_batch_call( relay_header_number: RelayBlockNumber, parachain_head_at_relay_header_number: RelayBlockNumber, @@ -1085,6 +1139,20 @@ mod tests { }) } + fn all_finality_and_delivery_batch_call_ex( + relay_header_number: RelayBlockNumber, + parachain_head_at_relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + submit_parachain_head_call(parachain_head_at_relay_header_number), + message_delivery_call(best_message), + ], + }) + } + fn all_finality_and_confirmation_batch_call( relay_header_number: RelayBlockNumber, parachain_head_at_relay_header_number: RelayBlockNumber, @@ -1099,12 +1167,27 @@ mod tests { }) } + fn all_finality_and_confirmation_batch_call_ex( + relay_header_number: RelayBlockNumber, + parachain_head_at_relay_header_number: RelayBlockNumber, + best_message: MessageNonce, + ) -> RuntimeCall { + RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![ + submit_relay_header_call_ex(relay_header_number), + submit_parachain_head_call(parachain_head_at_relay_header_number), + message_confirmation_call(best_message), + ], + }) + } + fn all_finality_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::AllFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1128,12 +1211,20 @@ mod tests { } } + fn all_finality_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = all_finality_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn all_finality_confirmation_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::AllFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1153,12 +1244,20 @@ mod tests { } } + fn all_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = all_finality_confirmation_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn relay_finality_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::RelayFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1177,12 +1276,20 @@ mod tests { } } + fn relay_finality_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = relay_finality_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn relay_finality_confirmation_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), call_info: CallInfo::RelayFinalityAndMsgs( SubmitFinalityProofInfo { block_number: 200, + current_set_id: None, extra_weight: Weight::zero(), extra_size: 0, }, @@ -1197,6 +1304,13 @@ mod tests { } } + fn relay_finality_confirmation_pre_dispatch_data_ex() -> PreDispatchData { + let mut data = relay_finality_confirmation_pre_dispatch_data(); + data.call_info.submit_finality_proof_info_mut().unwrap().current_set_id = + Some(TEST_GRANDPA_SET_ID); + data + } + fn parachain_finality_pre_dispatch_data() -> PreDispatchData { PreDispatchData { relayer: relayer_account_at_this_chain(), @@ -1393,6 +1507,10 @@ mod tests { run_validate(all_finality_and_delivery_batch_call(200, 200, 200)), Ok(Default::default()), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Ok(Default::default()), + ); // message confirmation validation is passing assert_eq!( run_validate_ignore_priority(message_confirmation_call(200)), @@ -1410,6 +1528,12 @@ mod tests { )), Ok(Default::default()), ); + assert_eq!( + run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + 200, 200, 200 + )), + Ok(Default::default()), + ); }); } @@ -1500,12 +1624,24 @@ mod tests { run_validate_ignore_priority(all_finality_and_delivery_batch_call(200, 200, 200)), Ok(ValidTransaction::default()), ); + assert_eq!( + run_validate_ignore_priority(all_finality_and_delivery_batch_call_ex( + 200, 200, 200 + )), + Ok(ValidTransaction::default()), + ); assert_eq!( run_validate_ignore_priority(all_finality_and_confirmation_batch_call( 200, 200, 200 )), Ok(ValidTransaction::default()), ); + assert_eq!( + run_validate_ignore_priority(all_finality_and_confirmation_batch_call_ex( + 200, 200, 200 + )), + Ok(ValidTransaction::default()), + ); }); } @@ -1518,11 +1654,19 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(100, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(100, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_delivery_batch_call(100, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(100, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); }); } @@ -1535,10 +1679,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(101, 100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(101, 100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_delivery_batch_call(101, 100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(101, 100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(100, 200)), @@ -1560,19 +1712,35 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_delivery_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_delivery_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_validate(all_finality_and_confirmation_batch_call(200, 200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_validate(all_finality_and_confirmation_batch_call_ex(200, 200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 100)), @@ -1609,10 +1777,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); }); } @@ -1631,10 +1807,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)), @@ -1662,10 +1846,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + ); assert_eq!( run_pre_dispatch(parachain_finality_and_delivery_batch_call(200, 200)), @@ -1696,10 +1888,18 @@ mod tests { run_pre_dispatch(all_finality_and_delivery_batch_call(200, 200, 200)), Ok(Some(all_finality_pre_dispatch_data())), ); + assert_eq!( + run_pre_dispatch(all_finality_and_delivery_batch_call_ex(200, 200, 200)), + Ok(Some(all_finality_pre_dispatch_data_ex())), + ); assert_eq!( run_pre_dispatch(all_finality_and_confirmation_batch_call(200, 200, 200)), Ok(Some(all_finality_confirmation_pre_dispatch_data())), ); + assert_eq!( + run_pre_dispatch(all_finality_and_confirmation_batch_call_ex(200, 200, 200)), + Ok(Some(all_finality_confirmation_pre_dispatch_data_ex())), + ); }); } @@ -2126,6 +2326,12 @@ mod tests { ), Ok(None), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_delivery_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); // relay + parachain + message confirmation calls batch is ignored assert_eq!( @@ -2134,6 +2340,12 @@ mod tests { ), Ok(None), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &all_finality_and_confirmation_batch_call_ex(200, 200, 200) + ), + Ok(None), + ); // parachain + message delivery call batch is ignored assert_eq!( @@ -2158,6 +2370,12 @@ mod tests { ), Ok(Some(relay_finality_pre_dispatch_data().call_info)), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_delivery_batch_call_ex(200, 200) + ), + Ok(Some(relay_finality_pre_dispatch_data_ex().call_info)), + ); // relay + message confirmation call batch is accepted assert_eq!( @@ -2166,6 +2384,12 @@ mod tests { ), Ok(Some(relay_finality_confirmation_pre_dispatch_data().call_info)), ); + assert_eq!( + TestGrandpaExtensionProvider::parse_and_check_for_obsolete_call( + &relay_finality_and_confirmation_batch_call_ex(200, 200) + ), + Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex().call_info)), + ); // message delivery call batch is accepted assert_eq!( @@ -2194,11 +2418,19 @@ mod tests { run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_validate(relay_finality_and_delivery_batch_call(100, 200)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(100, 200)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); }); } @@ -2211,19 +2443,35 @@ mod tests { run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 100)), Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 100)), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)), + ); assert_eq!( run_grandpa_pre_dispatch(message_delivery_call(100)), @@ -2254,19 +2502,35 @@ mod tests { run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call(200, 200)), Ok(Some(relay_finality_pre_dispatch_data()),) ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_delivery_batch_call_ex(200, 200)), + Ok(Some(relay_finality_pre_dispatch_data_ex()),) + ); assert_eq!( run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call(200, 200)), Ok(Some(relay_finality_confirmation_pre_dispatch_data())), ); + assert_eq!( + run_grandpa_pre_dispatch(relay_finality_and_confirmation_batch_call_ex(200, 200)), + Ok(Some(relay_finality_confirmation_pre_dispatch_data_ex())), + ); assert_eq!( run_grandpa_validate(relay_finality_and_delivery_batch_call(200, 200)), Ok(Default::default()), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_delivery_batch_call_ex(200, 200)), + Ok(Default::default()), + ); assert_eq!( run_grandpa_validate(relay_finality_and_confirmation_batch_call(200, 200)), Ok(Default::default()), ); + assert_eq!( + run_grandpa_validate(relay_finality_and_confirmation_batch_call_ex(200, 200)), + Ok(Default::default()), + ); assert_eq!( run_grandpa_pre_dispatch(message_delivery_call(200)), diff --git a/bridges/modules/grandpa/Cargo.toml b/bridges/modules/grandpa/Cargo.toml index 562a6e43e0271dadcf68f77f0a74bba887c862f5..dccd7b3bdca3533cda4fec82ed0266d0b221b7a7 100644 --- a/bridges/modules/grandpa/Cargo.toml +++ b/bridges/modules/grandpa/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } finality-grandpa = { version = "0.16.2", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge Dependencies diff --git a/bridges/modules/grandpa/README.md b/bridges/modules/grandpa/README.md index 27b4d2389c4085538362e6aea17303c44ef50795..43ee5c316d1b76ec8fc94b0c3819b1340a6ce75c 100644 --- a/bridges/modules/grandpa/README.md +++ b/bridges/modules/grandpa/README.md @@ -38,11 +38,11 @@ There are two main things in GRANDPA that help building light clients: ## Pallet Operations -The main entrypoint of the pallet is the `submit_finality_proof` call. It has two arguments - the finalized -headers and associated GRANDPA justification. The call simply verifies the justification using current -validators set and checks if header is better than the previous best header. If both checks are passed, the -header (only its useful fields) is inserted into the runtime storage and may be used by other pallets to verify -storage proofs. +The main entrypoint of the pallet is the `submit_finality_proof_ex` call. It has three arguments - the finalized +headers, associated GRANDPA justification and ID of the authority set that has generated this justification. The +call simply verifies the justification using current validators set and checks if header is better than the +previous best header. If both checks are passed, the header (only its useful fields) is inserted into the runtime +storage and may be used by other pallets to verify storage proofs. The submitter pays regular fee for submitting all headers, except for the mandatory header. Since it is required for the pallet operations, submitting such header is free. So if you're ok with session-length diff --git a/bridges/modules/grandpa/src/benchmarking.rs b/bridges/modules/grandpa/src/benchmarking.rs index 182b2f56eb1c57a165cf2eb1e86b585d70fd1801..11033373ce478fa9fefb613a1377449bb77daf1d 100644 --- a/bridges/modules/grandpa/src/benchmarking.rs +++ b/bridges/modules/grandpa/src/benchmarking.rs @@ -16,8 +16,9 @@ //! Benchmarks for the GRANDPA Pallet. //! -//! The main dispatchable for the GRANDPA pallet is `submit_finality_proof`, so these benchmarks are -//! based around that. There are to main factors which affect finality proof verification: +//! The main dispatchable for the GRANDPA pallet is `submit_finality_proof_ex`. Our benchmarks +//! are based around `submit_finality_proof`, though - from weight PoV they are the same calls. +//! There are to main factors which affect finality proof verification: //! //! 1. The number of `votes-ancestries` in the justification //! 2. The number of `pre-commits` in the justification diff --git a/bridges/modules/grandpa/src/call_ext.rs b/bridges/modules/grandpa/src/call_ext.rs index c1585020be13ca710178b59aefde4a0cde2ab87a..e3c778b480baa51a8b9e5d04564ac54bc7a68a21 100644 --- a/bridges/modules/grandpa/src/call_ext.rs +++ b/bridges/modules/grandpa/src/call_ext.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Parity Bridges Common. If not, see . -use crate::{weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, Error, Pallet}; +use crate::{ + weights::WeightInfo, BridgedBlockNumber, BridgedHeader, Config, CurrentAuthoritySet, Error, + Pallet, +}; use bp_header_chain::{ justification::GrandpaJustification, max_expected_submit_finality_proof_arguments_size, ChainWithGrandpa, GrandpaConsensusLogReader, @@ -22,6 +25,7 @@ use bp_header_chain::{ use bp_runtime::{BlockNumberOf, OwnedBridgeModule}; use codec::Encode; use frame_support::{dispatch::CallableCallFor, traits::IsSubType, weights::Weight}; +use sp_consensus_grandpa::SetId; use sp_runtime::{ traits::{Header, Zero}, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, @@ -33,6 +37,9 @@ use sp_runtime::{ pub struct SubmitFinalityProofInfo { /// Number of the finality target. pub block_number: N, + /// An identifier of the validators set that has signed the submitted justification. + /// It might be `None` if deprecated version of the `submit_finality_proof` is used. + pub current_set_id: Option, /// Extra weight that we assume is included in the call. /// /// We have some assumptions about headers and justifications of the bridged chain. @@ -61,9 +68,11 @@ pub struct SubmitFinalityProofHelper, I: 'static> { impl, I: 'static> SubmitFinalityProofHelper { /// Check that the GRANDPA head provided by the `SubmitFinalityProof` is better than the best - /// one we know. + /// one we know. Additionally, checks if `current_set_id` matches the current authority set + /// id, if specified. pub fn check_obsolete( finality_target: BlockNumberOf, + current_set_id: Option, ) -> Result<(), Error> { let best_finalized = crate::BestFinalized::::get().ok_or_else(|| { log::trace!( @@ -85,6 +94,20 @@ impl, I: 'static> SubmitFinalityProofHelper { return Err(Error::::OldHeader) } + if let Some(current_set_id) = current_set_id { + let actual_set_id = >::get().set_id; + if current_set_id != actual_set_id { + log::trace!( + target: crate::LOG_TARGET, + "Cannot finalize header signed by unknown authority set: bundled {:?}, best {:?}", + current_set_id, + actual_set_id, + ); + + return Err(Error::::InvalidAuthoritySetId) + } + } + Ok(()) } @@ -111,6 +134,18 @@ pub trait CallSubType, I: 'static>: return Some(submit_finality_proof_info_from_args::( finality_target, justification, + None, + )) + } else if let Some(crate::Call::::submit_finality_proof_ex { + finality_target, + justification, + current_set_id, + }) = self.is_sub_type() + { + return Some(submit_finality_proof_info_from_args::( + finality_target, + justification, + Some(*current_set_id), )) } @@ -133,7 +168,10 @@ pub trait CallSubType, I: 'static>: return InvalidTransaction::Call.into() } - match SubmitFinalityProofHelper::::check_obsolete(finality_target.block_number) { + match SubmitFinalityProofHelper::::check_obsolete( + finality_target.block_number, + finality_target.current_set_id, + ) { Ok(_) => Ok(ValidTransaction::default()), Err(Error::::OldHeader) => InvalidTransaction::Stale.into(), Err(_) => InvalidTransaction::Call.into(), @@ -150,6 +188,7 @@ impl, I: 'static> CallSubType for T::RuntimeCall where pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( finality_target: &BridgedHeader, justification: &GrandpaJustification>, + current_set_id: Option, ) -> SubmitFinalityProofInfo> { let block_number = *finality_target.number(); @@ -191,7 +230,7 @@ pub(crate) fn submit_finality_proof_info_from_args, I: 'static>( ); let extra_size = actual_call_size.saturating_sub(max_expected_call_size); - SubmitFinalityProofInfo { block_number, extra_weight, extra_size } + SubmitFinalityProofInfo { block_number, current_set_id, extra_weight, extra_size } } #[cfg(test)] @@ -199,20 +238,24 @@ mod tests { use crate::{ call_ext::CallSubType, mock::{run_test, test_header, RuntimeCall, TestBridgedChain, TestNumber, TestRuntime}, - BestFinalized, Config, PalletOperatingMode, WeightInfo, + BestFinalized, Config, CurrentAuthoritySet, PalletOperatingMode, StoredAuthoritySet, + SubmitFinalityProofInfo, WeightInfo, }; use bp_header_chain::ChainWithGrandpa; use bp_runtime::{BasicOperatingMode, HeaderId}; use bp_test_utils::{ make_default_justification, make_justification_for_header, JustificationGeneratorParams, + TEST_GRANDPA_SET_ID, }; use frame_support::weights::Weight; use sp_runtime::{testing::DigestItem, traits::Header as _, SaturatedConversion}; fn validate_block_submit(num: TestNumber) -> bool { - let bridge_grandpa_call = crate::Call::::submit_finality_proof { + let bridge_grandpa_call = crate::Call::::submit_finality_proof_ex { finality_target: Box::new(test_header(num)), justification: make_default_justification(&test_header(num)), + // not initialized => zero + current_set_id: 0, }; RuntimeCall::check_obsolete_submit_finality_proof(&RuntimeCall::Grandpa( bridge_grandpa_call, @@ -256,6 +299,18 @@ mod tests { }); } + #[test] + fn extension_rejects_new_header_if_set_id_is_invalid() { + run_test(|| { + // when set id is different from the passed one => tx is rejected + sync_to_header_10(); + let next_set = StoredAuthoritySet::::try_new(vec![], 0x42).unwrap(); + CurrentAuthoritySet::::put(next_set); + + assert!(!validate_block_submit(15)); + }); + } + #[test] fn extension_accepts_new_header() { run_test(|| { @@ -266,6 +321,42 @@ mod tests { }); } + #[test] + fn submit_finality_proof_info_is_parsed() { + // when `submit_finality_proof` is used, `current_set_id` is set to `None` + let deprecated_call = + RuntimeCall::Grandpa(crate::Call::::submit_finality_proof { + finality_target: Box::new(test_header(42)), + justification: make_default_justification(&test_header(42)), + }); + assert_eq!( + deprecated_call.submit_finality_proof_info(), + Some(SubmitFinalityProofInfo { + block_number: 42, + current_set_id: None, + extra_weight: Weight::zero(), + extra_size: 0, + }) + ); + + // when `submit_finality_proof_ex` is used, `current_set_id` is set to `Some` + let deprecated_call = + RuntimeCall::Grandpa(crate::Call::::submit_finality_proof_ex { + finality_target: Box::new(test_header(42)), + justification: make_default_justification(&test_header(42)), + current_set_id: 777, + }); + assert_eq!( + deprecated_call.submit_finality_proof_info(), + Some(SubmitFinalityProofInfo { + block_number: 42, + current_set_id: Some(777), + extra_weight: Weight::zero(), + extra_size: 0, + }) + ); + } + #[test] fn extension_returns_correct_extra_size_if_call_arguments_are_too_large() { // when call arguments are below our limit => no refund @@ -275,9 +366,10 @@ mod tests { ..Default::default() }; let small_justification = make_justification_for_header(justification_params); - let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let small_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(small_finality_target), justification: small_justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_eq!(small_call.submit_finality_proof_info().unwrap().extra_size, 0); @@ -291,9 +383,10 @@ mod tests { ..Default::default() }; let large_justification = make_justification_for_header(justification_params); - let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let large_call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(large_finality_target), justification: large_justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_ne!(large_call.submit_finality_proof_info().unwrap().extra_size, 0); } @@ -309,9 +402,10 @@ mod tests { // when there are `REASONABLE_HEADERS_IN_JUSTIFICATON_ANCESTRY` headers => no refund let justification = make_justification_for_header(justification_params.clone()); - let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(finality_target.clone()), justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, Weight::zero()); @@ -322,9 +416,10 @@ mod tests { justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ); - let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof { + let call = RuntimeCall::Grandpa(crate::Call::submit_finality_proof_ex { finality_target: Box::new(finality_target), justification, + current_set_id: TEST_GRANDPA_SET_ID, }); assert_eq!(call.submit_finality_proof_info().unwrap().extra_weight, call_weight); } diff --git a/bridges/modules/grandpa/src/lib.rs b/bridges/modules/grandpa/src/lib.rs index f58db2481ada11e29e3cfe40315d95f468aa82cf..ce2c47da954fa46efc4c70e9608864735fa16277 100644 --- a/bridges/modules/grandpa/src/lib.rs +++ b/bridges/modules/grandpa/src/lib.rs @@ -151,7 +151,86 @@ pub mod pallet { #[pallet::call] impl, I: 'static> Pallet { - /// Verify a target header is finalized according to the given finality proof. + /// This call is deprecated and will be removed around May 2024. Use the + /// `submit_finality_proof_ex` instead. Semantically, this call is an equivalent of the + /// `submit_finality_proof_ex` call without current authority set id check. + #[pallet::call_index(0)] + #[pallet::weight(::submit_finality_proof( + justification.commit.precommits.len().saturated_into(), + justification.votes_ancestries.len().saturated_into(), + ))] + #[allow(deprecated)] + #[deprecated( + note = "`submit_finality_proof` will be removed in May 2024. Use `submit_finality_proof_ex` instead." + )] + pub fn submit_finality_proof( + origin: OriginFor, + finality_target: Box>, + justification: GrandpaJustification>, + ) -> DispatchResultWithPostInfo { + Self::submit_finality_proof_ex( + origin, + finality_target, + justification, + // the `submit_finality_proof_ex` also reads this value, but it is done from the + // cache, so we don't treat it as an additional db access + >::get().set_id, + ) + } + + /// Bootstrap the bridge pallet with an initial header and authority set from which to sync. + /// + /// The initial configuration provided does not need to be the genesis header of the bridged + /// chain, it can be any arbitrary header. You can also provide the next scheduled set + /// change if it is already know. + /// + /// This function is only allowed to be called from a trusted origin and writes to storage + /// with practically no checks in terms of the validity of the data. It is important that + /// you ensure that valid data is being passed in. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(2, 5), DispatchClass::Operational))] + pub fn initialize( + origin: OriginFor, + init_data: super::InitializationData>, + ) -> DispatchResultWithPostInfo { + Self::ensure_owner_or_root(origin)?; + + let init_allowed = !>::exists(); + ensure!(init_allowed, >::AlreadyInitialized); + initialize_bridge::(init_data.clone())?; + + log::info!( + target: LOG_TARGET, + "Pallet has been initialized with the following parameters: {:?}", + init_data + ); + + Ok(().into()) + } + + /// Change `PalletOwner`. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(2)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { + >::set_owner(origin, new_owner) + } + + /// Halt or resume all pallet operations. + /// + /// May only be called either by root, or by `PalletOwner`. + #[pallet::call_index(3)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + operating_mode: BasicOperatingMode, + ) -> DispatchResult { + >::set_operating_mode(origin, operating_mode) + } + + /// Verify a target header is finalized according to the given finality proof. The proof + /// is assumed to be signed by GRANDPA authorities set with `current_set_id` id. /// /// It will use the underlying storage pallet to fetch information about the current /// authorities and best finalized header in order to verify that the header is finalized. @@ -165,18 +244,22 @@ pub mod pallet { /// /// - the pallet knows better header than the `finality_target`; /// + /// - the id of best GRANDPA authority set, known to the pallet is not equal to the + /// `current_set_id`; + /// /// - verification is not optimized or invalid; /// /// - header contains forced authorities set change or change with non-zero delay. - #[pallet::call_index(0)] + #[pallet::call_index(4)] #[pallet::weight(::submit_finality_proof( justification.commit.precommits.len().saturated_into(), justification.votes_ancestries.len().saturated_into(), ))] - pub fn submit_finality_proof( + pub fn submit_finality_proof_ex( origin: OriginFor, finality_target: Box>, justification: GrandpaJustification>, + current_set_id: sp_consensus_grandpa::SetId, ) -> DispatchResultWithPostInfo { Self::ensure_not_halted().map_err(Error::::BridgeModule)?; ensure_signed(origin)?; @@ -188,7 +271,9 @@ pub mod pallet { finality_target ); - SubmitFinalityProofHelper::::check_obsolete(number)?; + // it checks whether the `number` is better than the current best block number + // and whether the `current_set_id` matches the best known set id + SubmitFinalityProofHelper::::check_obsolete(number, Some(current_set_id))?; let authority_set = >::get(); let unused_proof_size = authority_set.unused_proof_size(); @@ -202,7 +287,7 @@ pub mod pallet { // if we have seen too many mandatory headers in this block, we don't want to refund Self::free_mandatory_headers_remaining() > 0 && // if arguments out of expected bounds, we don't want to refund - submit_finality_proof_info_from_args::(&finality_target, &justification) + submit_finality_proof_info_from_args::(&finality_target, &justification, Some(current_set_id)) .fits_limits(); if may_refund_call_fee { FreeMandatoryHeadersRemaining::::mutate(|count| { @@ -248,57 +333,6 @@ pub mod pallet { Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee }) } - - /// Bootstrap the bridge pallet with an initial header and authority set from which to sync. - /// - /// The initial configuration provided does not need to be the genesis header of the bridged - /// chain, it can be any arbitrary header. You can also provide the next scheduled set - /// change if it is already know. - /// - /// This function is only allowed to be called from a trusted origin and writes to storage - /// with practically no checks in terms of the validity of the data. It is important that - /// you ensure that valid data is being passed in. - #[pallet::call_index(1)] - #[pallet::weight((T::DbWeight::get().reads_writes(2, 5), DispatchClass::Operational))] - pub fn initialize( - origin: OriginFor, - init_data: super::InitializationData>, - ) -> DispatchResultWithPostInfo { - Self::ensure_owner_or_root(origin)?; - - let init_allowed = !>::exists(); - ensure!(init_allowed, >::AlreadyInitialized); - initialize_bridge::(init_data.clone())?; - - log::info!( - target: LOG_TARGET, - "Pallet has been initialized with the following parameters: {:?}", - init_data - ); - - Ok(().into()) - } - - /// Change `PalletOwner`. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(2)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_owner(origin: OriginFor, new_owner: Option) -> DispatchResult { - >::set_owner(origin, new_owner) - } - - /// Halt or resume all pallet operations. - /// - /// May only be called either by root, or by `PalletOwner`. - #[pallet::call_index(3)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_operating_mode( - origin: OriginFor, - operating_mode: BasicOperatingMode, - ) -> DispatchResult { - >::set_operating_mode(origin, operating_mode) - } } /// Number mandatory headers that we may accept in the current block for free (returning @@ -436,6 +470,9 @@ pub mod pallet { TooManyAuthoritiesInSet, /// Error generated by the `OwnedBridgeModule` trait. BridgeModule(bp_runtime::OwnedBridgeModuleError), + /// The `current_set_id` argument of the `submit_finality_proof_ex` doesn't match + /// the id of the current set, known to the pallet. + InvalidAuthoritySetId, } /// Check the given header for a GRANDPA scheduled authority set change. If a change @@ -663,6 +700,7 @@ mod tests { use bp_test_utils::{ authority_list, generate_owned_bridge_module_tests, make_default_justification, make_justification_for_header, JustificationGeneratorParams, ALICE, BOB, + TEST_GRANDPA_SET_ID, }; use codec::Encode; use frame_support::{ @@ -693,7 +731,7 @@ mod tests { let init_data = InitializationData { header: Box::new(genesis), authority_list: authority_list(), - set_id: 1, + set_id: TEST_GRANDPA_SET_ID, operating_mode: BasicOperatingMode::Normal, }; @@ -704,10 +742,11 @@ mod tests { let header = test_header(header.into()); let justification = make_default_justification(&header); - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ) } @@ -722,10 +761,11 @@ mod tests { ..Default::default() }); - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + set_id, ) } @@ -749,10 +789,11 @@ mod tests { ..Default::default() }); - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + set_id, ) } @@ -955,17 +996,30 @@ mod tests { let header = test_header(1); - let params = - JustificationGeneratorParams:: { set_id: 2, ..Default::default() }; + let next_set_id = 2; + let params = JustificationGeneratorParams:: { + set_id: next_set_id, + ..Default::default() + }; let justification = make_justification_for_header(params); assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( + RuntimeOrigin::signed(1), + Box::new(header.clone()), + justification.clone(), + TEST_GRANDPA_SET_ID, + ), + >::InvalidJustification + ); + assert_err!( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + next_set_id, ), - >::InvalidJustification + >::InvalidAuthoritySetId ); }) } @@ -980,10 +1034,11 @@ mod tests { justification.round = 42; assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ), >::InvalidJustification ); @@ -1009,10 +1064,11 @@ mod tests { let justification = make_default_justification(&header); assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ), >::InvalidAuthoritySet ); @@ -1047,10 +1103,11 @@ mod tests { let justification = make_default_justification(&header); // Let's import our test header - let result = Pallet::::submit_finality_proof( + let result = Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header.clone()), justification.clone(), + TEST_GRANDPA_SET_ID, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::No); @@ -1109,10 +1166,11 @@ mod tests { // without large digest item ^^^ the relayer would have paid zero transaction fee // (`Pays::No`) - let result = Pallet::::submit_finality_proof( + let result = Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header.clone()), justification, + TEST_GRANDPA_SET_ID, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1140,10 +1198,11 @@ mod tests { // without many headers in votes ancestries ^^^ the relayer would have paid zero // transaction fee (`Pays::No`) - let result = Pallet::::submit_finality_proof( + let result = Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header.clone()), justification, + TEST_GRANDPA_SET_ID, ); assert_ok!(result); assert_eq!(result.unwrap().pays_fee, frame_support::dispatch::Pays::Yes); @@ -1169,10 +1228,11 @@ mod tests { // Should not be allowed to import this header assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), - justification + justification, + TEST_GRANDPA_SET_ID, ), >::UnsupportedScheduledChange ); @@ -1194,10 +1254,11 @@ mod tests { // Should not be allowed to import this header assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), - justification + justification, + TEST_GRANDPA_SET_ID, ), >::UnsupportedScheduledChange ); @@ -1219,10 +1280,11 @@ mod tests { // Should not be allowed to import this header assert_err!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), - justification + justification, + TEST_GRANDPA_SET_ID, ), >::TooManyAuthoritiesInSet ); @@ -1283,10 +1345,11 @@ mod tests { let mut invalid_justification = make_default_justification(&header); invalid_justification.round = 42; - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), invalid_justification, + TEST_GRANDPA_SET_ID, ) }; @@ -1451,10 +1514,11 @@ mod tests { let justification = make_default_justification(&header); assert_noop!( - Pallet::::submit_finality_proof( + Pallet::::submit_finality_proof_ex( RuntimeOrigin::root(), Box::new(header), justification, + TEST_GRANDPA_SET_ID, ), DispatchError::BadOrigin, ); diff --git a/bridges/modules/messages/Cargo.toml b/bridges/modules/messages/Cargo.toml index 224aad7b36a2fd217fa910bbb49ed4474046b30e..173d6f1c16448517b7051cfba2f96625ff3d525a 100644 --- a/bridges/modules/messages/Cargo.toml +++ b/bridges/modules/messages/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } num-traits = { version = "0.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/bridges/modules/parachains/Cargo.toml b/bridges/modules/parachains/Cargo.toml index 41aeca4b3bc5c608915783d2603ef0157aef54bb..e454a6f2888fa169a0b0795101172b2f260b4020 100644 --- a/bridges/modules/parachains/Cargo.toml +++ b/bridges/modules/parachains/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge Dependencies diff --git a/bridges/modules/parachains/src/lib.rs b/bridges/modules/parachains/src/lib.rs index 87c57e84622aa7cb5eb1019ec88a680dc06fd8ee..1363a637604d1202ffc4bf799bf7ced180d9fe53 100644 --- a/bridges/modules/parachains/src/lib.rs +++ b/bridges/modules/parachains/src/lib.rs @@ -731,6 +731,7 @@ pub(crate) mod tests { }; use bp_test_utils::{ authority_list, generate_owned_bridge_module_tests, make_default_justification, + TEST_GRANDPA_SET_ID, }; use frame_support::{ assert_noop, assert_ok, @@ -777,10 +778,11 @@ pub(crate) mod tests { let hash = header.hash(); let justification = make_default_justification(&header); assert_ok!( - pallet_bridge_grandpa::Pallet::::submit_finality_proof( + pallet_bridge_grandpa::Pallet::::submit_finality_proof_ex( RuntimeOrigin::signed(1), Box::new(header), justification.clone(), + TEST_GRANDPA_SET_ID, ) ); diff --git a/bridges/modules/relayers/Cargo.toml b/bridges/modules/relayers/Cargo.toml index 35017ebbd30361ae5c6184cfdbbdd7a42e9d59bb..b78da5cbeeca65a4f448cbc38928894d51e8f7b4 100644 --- a/bridges/modules/relayers/Cargo.toml +++ b/bridges/modules/relayers/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge dependencies diff --git a/bridges/modules/xcm-bridge-hub-router/Cargo.toml b/bridges/modules/xcm-bridge-hub-router/Cargo.toml index ff33b19a5811e2de96c276920fc8e354ca73ec19..20f8ff4407b2ad9882c64b334fa557a6c7dc4ef2 100644 --- a/bridges/modules/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub-router/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive", "serde"] } # Bridge dependencies diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml index 89e261dc6d79cd41d3afda77b37e4b48f4563c36..e10119e864953f1777c43151092ae43a5e594b8c 100644 --- a/bridges/modules/xcm-bridge-hub/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Bridge Dependencies diff --git a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs index 285f00204810bca4e392eda23f56e590ccae0401..c49aa4b856397d28746d017fd8333ae3ad10655e 100644 --- a/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-cumulus/src/lib.rs @@ -57,6 +57,12 @@ const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts(constants::WEIGHT_REF_TI .saturating_div(2) .set_proof_size(polkadot_primitives::MAX_POV_SIZE as u64); +/// We allow for 2 seconds of compute with a 6 second average block. +const MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING: Weight = Weight::from_parts( + constants::WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + polkadot_primitives::MAX_POV_SIZE as u64, +); + /// All cumulus bridge hubs assume that about 5 percent of the block weight is consumed by /// `on_initialize` handlers. This is used to limit the maximal weight of a single extrinsic. /// @@ -96,6 +102,26 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); + + /// Weight limit of the Cumulus-based bridge hub blocks when async backing is enabled. + pub BlockWeightsForAsyncBacking: limits::BlockWeights = limits::BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING); + // Operational transactions have an extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT_FOR_ASYNC_BACKING, + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); } /// Public key of the chain account that may be used to verify signatures. diff --git a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs index 7b109f30fe0b9700a513282640fd2baa6cba43f5..c4e697fbe9526b85c7f10cf739c6893d50190fe9 100644 --- a/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-rococo/src/lib.rs @@ -50,7 +50,7 @@ impl Chain for BridgeHubRococo { } fn max_extrinsic_weight() -> Weight { - BlockWeights::get() + BlockWeightsForAsyncBacking::get() .get(DispatchClass::Normal) .max_extrinsic .unwrap_or(Weight::MAX) @@ -99,7 +99,7 @@ frame_support::parameter_types! { /// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Rococo /// BridgeHub. /// (initially was calculated by test `BridgeHubRococo::can_calculate_weight_for_paid_export_message_with_reserve_transfer` + `33%`) - pub const BridgeHubRococoBaseXcmFeeInRocs: u128 = 1_640_102_205; + pub const BridgeHubRococoBaseXcmFeeInRocs: u128 = 59_034_266; /// Transaction fee that is paid at the Rococo BridgeHub for delivering single inbound message. /// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) @@ -107,5 +107,5 @@ frame_support::parameter_types! { /// Transaction fee that is paid at the Rococo BridgeHub for delivering single outbound message confirmation. /// (initially was calculated by test `BridgeHubRococo::can_calculate_fee_for_complex_message_confirmation_transaction` + `33%`) - pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 4_045_736_577; + pub const BridgeHubRococoBaseConfirmationFeeInRocs: u128 = 5_380_829_647; } diff --git a/bridges/primitives/chain-bridge-hub-westend/src/lib.rs b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs index 83d4d6e33a75907f4400200b16cd19ed1c361ec1..4af895cc6d328bdb350fa95b0e0a74f0cc731b04 100644 --- a/bridges/primitives/chain-bridge-hub-westend/src/lib.rs +++ b/bridges/primitives/chain-bridge-hub-westend/src/lib.rs @@ -49,7 +49,7 @@ impl Chain for BridgeHubWestend { } fn max_extrinsic_weight() -> Weight { - BlockWeights::get() + BlockWeightsForAsyncBacking::get() .get(DispatchClass::Normal) .max_extrinsic .unwrap_or(Weight::MAX) @@ -90,7 +90,7 @@ frame_support::parameter_types! { /// The XCM fee that is paid for executing XCM program (with `ExportMessage` instruction) at the Westend /// BridgeHub. /// (initially was calculated by test `BridgeHubWestend::can_calculate_weight_for_paid_export_message_with_reserve_transfer` + `33%`) - pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 492_077_333_333; + pub const BridgeHubWestendBaseXcmFeeInWnds: u128 = 17_756_830_000; /// Transaction fee that is paid at the Westend BridgeHub for delivering single inbound message. /// (initially was calculated by test `BridgeHubWestend::can_calculate_fee_for_complex_message_delivery_transaction` + `33%`) diff --git a/bridges/primitives/header-chain/Cargo.toml b/bridges/primitives/header-chain/Cargo.toml index 828b567bb947d851e5d0f8dac03dadebf120e614..205b593365ef8216a2e501e5751303185d4f7537 100644 --- a/bridges/primitives/header-chain/Cargo.toml +++ b/bridges/primitives/header-chain/Cargo.toml @@ -13,7 +13,7 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } finality-grandpa = { version = "0.16.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } # Bridge dependencies diff --git a/bridges/primitives/header-chain/src/lib.rs b/bridges/primitives/header-chain/src/lib.rs index f5485aca1ee8b485eedd8b26874e401ceb5f4ff5..84a6a881a835b8afc3b5cde8992df1733859d29a 100644 --- a/bridges/primitives/header-chain/src/lib.rs +++ b/bridges/primitives/header-chain/src/lib.rs @@ -244,6 +244,16 @@ pub enum BridgeGrandpaCall { /// All data, required to initialize the pallet. init_data: InitializationData
, }, + /// `pallet-bridge-grandpa::Call::submit_finality_proof_ex` + #[codec(index = 4)] + submit_finality_proof_ex { + /// The header that we are going to finalize. + finality_target: Box
, + /// Finality justification for the `finality_target`. + justification: justification::GrandpaJustification
, + /// An identifier of the validators set, that have signed the justification. + current_set_id: SetId, + }, } /// The `BridgeGrandpaCall` used by a chain. diff --git a/bridges/primitives/messages/Cargo.toml b/bridges/primitives/messages/Cargo.toml index 54aae4a2f76a425046860835ccd2a66cc29cf135..8aa6b4b05e5efb2427a8548e91ec5f47ab494968 100644 --- a/bridges/primitives/messages/Cargo.toml +++ b/bridges/primitives/messages/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["bit-vec", "derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } # Bridge dependencies diff --git a/bridges/primitives/polkadot-core/Cargo.toml b/bridges/primitives/polkadot-core/Cargo.toml index e667c283a07937ab88053c064fb7b8cfb380f765..c0dae684b5f2f3b7b9be096a808fc67d15dadfcf 100644 --- a/bridges/primitives/polkadot-core/Cargo.toml +++ b/bridges/primitives/polkadot-core/Cargo.toml @@ -13,7 +13,7 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false, features = ["derive"] } parity-util-mem = { version = "0.12.0", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Bridge Dependencies diff --git a/bridges/primitives/runtime/Cargo.toml b/bridges/primitives/runtime/Cargo.toml index 6786bf8f21ced12e2424ecc17ff0c4ce96fd96d7..22206fb2c376ce53fee9dc8ff806baaef3ce7c28 100644 --- a/bridges/primitives/runtime/Cargo.toml +++ b/bridges/primitives/runtime/Cargo.toml @@ -13,10 +13,10 @@ workspace = true codec = { package = "parity-scale-codec", version = "3.1.5", default-features = false } hash-db = { version = "0.16.0", default-features = false } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.19", default-features = false } +log = { workspace = true } num-traits = { version = "0.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } # Substrate Dependencies diff --git a/bridges/snowbridge/README.md b/bridges/snowbridge/README.md index 49b9c2eaf553780176897a770bad9579d53bfaa9..6561df401120e9c5c5d6ee2762eb1423b5d6daaf 100644 --- a/bridges/snowbridge/README.md +++ b/bridges/snowbridge/README.md @@ -1,32 +1,40 @@ -# Snowbridge -[![codecov](https://codecov.io/gh/Snowfork/snowbridge/branch/main/graph/badge.svg?token=9hvgSws4rN)](https://codecov.io/gh/Snowfork/snowbridge) +# Snowbridge · +[![codecov](https://codecov.io/gh/Snowfork/polkadot-sdk/branch/snowbridge/graph/badge.svg?token=9hvgSws4rN)](https://codecov.io/gh/Snowfork/polkadot-sdk) ![GitHub](https://img.shields.io/github/license/Snowfork/snowbridge) Snowbridge is a trustless bridge between Polkadot and Ethereum. For documentation, visit https://docs.snowbridge.network. ## Components +The Snowbridge project lives in two repositories: + +- [Snowfork/Polkadot-sdk](https://github.com/Snowfork/polkadot-sdk): The Snowbridge parachain and pallets live in +a fork of the Polkadot SDK. Changes are eventually contributed back to +[paritytech/Polkadot-sdk](https://github.com/paritytech/polkadot-sdk) +- [Snowfork/snowbridge](https://github.com/Snowfork/snowbridge): The rest of the Snowbridge components, like contracts, +off-chain relayer, end-to-end tests and test-net setup code. + ### Parachain -Polkadot parachain and our pallets. See [parachain/README.md](https://github.com/Snowfork/snowbridge/blob/main/parachain/README.md). +Polkadot parachain and our pallets. See [README.md](https://github.com/Snowfork/polkadot-sdk/blob/snowbridge/bridges/snowbridge/README.md). ### Contracts -Ethereum contracts and unit tests. See [contracts/README.md](https://github.com/Snowfork/snowbridge/blob/main/contracts/README.md) +Ethereum contracts and unit tests. See [Snowfork/snowbridge/contracts/README.md](https://github.com/Snowfork/snowbridge/blob/main/contracts/README.md) ### Relayer Off-chain relayer services for relaying messages between Polkadot and Ethereum. See -[relayer/README.md](https://github.com/Snowfork/snowbridge/blob/main/relayer/README.md) +[Snowfork/snowbridge/relayer/README.md](https://github.com/Snowfork/snowbridge/blob/main/relayer/README.md) ### Local Testnet Scripts to provision a local testnet, running the above services to bridge between local deployments of Polkadot and -Ethereum. See [web/packages/test/README.md](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test/README.md). +Ethereum. See [Snowfork/snowbridge/web/packages/test/README.md](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test/README.md). ### Smoke Tests -Integration tests for our local testnet. See [smoketest/README.md](https://github.com/Snowfork/snowbridge/blob/main/smoketest/README.md). +Integration tests for our local testnet. See [Snowfork/snowbridge/smoketest/README.md](https://github.com/Snowfork/snowbridge/blob/main/smoketest/README.md). ## Development @@ -83,7 +91,7 @@ direnv allow ### Upgrading the Rust toolchain -Sometimes we would like to upgrade rust toolchain. First update `parachain/rust-toolchain.toml` as required and then +Sometimes we would like to upgrade rust toolchain. First update `rust-toolchain.toml` as required and then update `flake.lock` running ```sh nix flake lock --update-input rust-overlay diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml similarity index 65% rename from bridges/snowbridge/parachain/pallets/ethereum-client/Cargo.toml rename to bridges/snowbridge/pallets/ethereum-client/Cargo.toml index c490f3af73c2e8839cc37e22c6a92339c8558822..99b4290531145dba85521574a6d5004e7696cb08 100644 --- a/bridges/snowbridge/parachain/pallets/ethereum-client/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -15,8 +15,8 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", optional = true } -serde_json = { version = "1.0.111", optional = true } +serde = { optional = true, workspace = true, default-features = true } +serde_json = { optional = true, workspace = true, default-features = true } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } ssz_rs = { version = "0.9.0", default-features = false } @@ -24,33 +24,33 @@ ssz_rs_derive = { version = "0.9.0", default-features = false } byte-slice-cast = { version = "1.2.1", default-features = false } rlp = { version = "0.5.2", default-features = false } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false, optional = true } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false, optional = true } snowbridge-core = { path = "../../primitives/core", default-features = false } snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false } snowbridge-pallet-ethereum-client-fixtures = { path = "./fixtures", default-features = false, optional = true } primitives = { package = "snowbridge-beacon-primitives", path = "../../primitives/beacon", default-features = false } static_assertions = { version = "1.1.0", default-features = false } -bp-runtime = { path = "../../../../primitives/runtime", default-features = false } -pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false, optional = true } +bp-runtime = { path = "../../../primitives/runtime", default-features = false } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-features = false, optional = true } [dev-dependencies] rand = "0.8.5" -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } -serde_json = "1.0.111" +sp-keyring = { path = "../../../../substrate/primitives/keyring" } +serde_json = { workspace = true, default-features = true } hex-literal = "0.4.1" -pallet-timestamp = { path = "../../../../../substrate/frame/timestamp" } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp" } snowbridge-pallet-ethereum-client-fixtures = { path = "./fixtures" } -sp-io = { path = "../../../../../substrate/primitives/io" } -serde = "1.0.195" +sp-io = { path = "../../../../substrate/primitives/io" } +serde = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/README.md b/bridges/snowbridge/pallets/ethereum-client/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/README.md rename to bridges/snowbridge/pallets/ethereum-client/README.md diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/benchmark.md b/bridges/snowbridge/pallets/ethereum-client/benchmark.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/benchmark.md rename to bridges/snowbridge/pallets/ethereum-client/benchmark.md diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml similarity index 67% rename from bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/Cargo.toml rename to bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml index f3c26bbbe31b4151e7d1d6d64d48df63577b4abd..fd1914a7d30e81c3615c981d7ae8ba378a8a5724 100644 --- a/bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hex-literal = { version = "0.4.1" } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../../substrate/primitives/std", default-features = false } -frame-benchmarking = { path = "../../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } snowbridge-core = { path = "../../../primitives/core", default-features = false } snowbridge-beacon-primitives = { path = "../../../primitives/beacon", default-features = false } diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/src/lib.rs b/bridges/snowbridge/pallets/ethereum-client/fixtures/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/fixtures/src/lib.rs rename to bridges/snowbridge/pallets/ethereum-client/fixtures/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/benchmarking/mod.rs b/bridges/snowbridge/pallets/ethereum-client/src/benchmarking/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/benchmarking/mod.rs rename to bridges/snowbridge/pallets/ethereum-client/src/benchmarking/mod.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/benchmarking/util.rs b/bridges/snowbridge/pallets/ethereum-client/src/benchmarking/util.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/benchmarking/util.rs rename to bridges/snowbridge/pallets/ethereum-client/src/benchmarking/util.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/config/mod.rs b/bridges/snowbridge/pallets/ethereum-client/src/config/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/config/mod.rs rename to bridges/snowbridge/pallets/ethereum-client/src/config/mod.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/functions.rs b/bridges/snowbridge/pallets/ethereum-client/src/functions.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/functions.rs rename to bridges/snowbridge/pallets/ethereum-client/src/functions.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/impls.rs b/bridges/snowbridge/pallets/ethereum-client/src/impls.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/impls.rs rename to bridges/snowbridge/pallets/ethereum-client/src/impls.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/lib.rs b/bridges/snowbridge/pallets/ethereum-client/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/lib.rs rename to bridges/snowbridge/pallets/ethereum-client/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/mock.rs b/bridges/snowbridge/pallets/ethereum-client/src/mock.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/mock.rs rename to bridges/snowbridge/pallets/ethereum-client/src/mock.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/tests.rs b/bridges/snowbridge/pallets/ethereum-client/src/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/tests.rs rename to bridges/snowbridge/pallets/ethereum-client/src/tests.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/types.rs b/bridges/snowbridge/pallets/ethereum-client/src/types.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/types.rs rename to bridges/snowbridge/pallets/ethereum-client/src/types.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/src/weights.rs b/bridges/snowbridge/pallets/ethereum-client/src/weights.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/src/weights.rs rename to bridges/snowbridge/pallets/ethereum-client/src/weights.rs diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/execution-header-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/execution-header-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/execution-header-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/execution-header-update.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/finalized-header-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/finalized-header-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/finalized-header-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/finalized-header-update.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/inbound-message.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/inbound-message.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/inbound-message.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/inbound-message.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/initial-checkpoint.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-finalized-header-update.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/next-sync-committee-update.json diff --git a/bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/sync-committee-update.json b/bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update.json similarity index 100% rename from bridges/snowbridge/parachain/pallets/ethereum-client/tests/fixtures/sync-committee-update.json rename to bridges/snowbridge/pallets/ethereum-client/tests/fixtures/sync-committee-update.json diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml similarity index 71% rename from bridges/snowbridge/parachain/pallets/inbound-queue/Cargo.toml rename to bridges/snowbridge/pallets/inbound-queue/Cargo.toml index 367e00ad46a110be1afe58445d7792af5bab345a..c26ef90a22dbeca766cb7064c1947b52c2e99794 100644 --- a/bridges/snowbridge/parachain/pallets/inbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -15,28 +15,28 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } alloy-primitives = { version = "0.4.2", default-features = false, features = ["rlp"] } alloy-sol-types = { version = "0.4.2", default-features = false } alloy-rlp = { version = "0.3.3", default-features = false, features = ["derive"] } num-traits = { version = "0.2.16", default-features = false } -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } snowbridge-core = { path = "../../primitives/core", default-features = false } snowbridge-ethereum = { path = "../../primitives/ethereum", default-features = false } @@ -45,8 +45,8 @@ snowbridge-beacon-primitives = { path = "../../primitives/beacon", default-featu snowbridge-pallet-inbound-queue-fixtures = { path = "./fixtures", default-features = false, optional = true } [dev-dependencies] -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking" } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking" } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } snowbridge-pallet-ethereum-client = { path = "../ethereum-client" } hex-literal = { version = "0.4.1" } diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/README.md b/bridges/snowbridge/pallets/inbound-queue/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/README.md rename to bridges/snowbridge/pallets/inbound-queue/README.md diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml similarity index 67% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/Cargo.toml rename to bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml index 7ed2b73151976b162afdb8ee49fea7597c2a078e..61f1421e056773c4f078390f9c48f7b8fa0420d3 100644 --- a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hex-literal = { version = "0.4.1" } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../../substrate/primitives/std", default-features = false } -frame-benchmarking = { path = "../../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../../substrate/frame/system", default-features = false } snowbridge-core = { path = "../../../primitives/core", default-features = false } snowbridge-beacon-primitives = { path = "../../../primitives/beacon", default-features = false } diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/lib.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/register_token.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/register_token.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/register_token_with_insufficient_fee.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/send_token.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/send_token.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs rename to bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_token_to_penpal.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/mod.rs b/bridges/snowbridge/pallets/inbound-queue/src/benchmarking/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/benchmarking/mod.rs rename to bridges/snowbridge/pallets/inbound-queue/src/benchmarking/mod.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/envelope.rs rename to bridges/snowbridge/pallets/inbound-queue/src/envelope.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/lib.rs rename to bridges/snowbridge/pallets/inbound-queue/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/mock.rs rename to bridges/snowbridge/pallets/inbound-queue/src/mock.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/test.rs rename to bridges/snowbridge/pallets/inbound-queue/src/test.rs diff --git a/bridges/snowbridge/parachain/pallets/inbound-queue/src/weights.rs b/bridges/snowbridge/pallets/inbound-queue/src/weights.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/inbound-queue/src/weights.rs rename to bridges/snowbridge/pallets/inbound-queue/src/weights.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml similarity index 59% rename from bridges/snowbridge/parachain/pallets/outbound-queue/Cargo.toml rename to bridges/snowbridge/pallets/outbound-queue/Cargo.toml index d6add7e348cdaff48ded64b6e2ab94ae9ebe02f1..40e57db4bbd9e5d6e32cee84e586a8858b2222cf 100644 --- a/bridges/snowbridge/parachain/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -15,31 +15,31 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false } +serde = { features = ["alloc", "derive"], workspace = true } codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-arithmetic = { path = "../../../../../substrate/primitives/arithmetic", default-features = false } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-arithmetic = { path = "../../../../substrate/primitives/arithmetic", default-features = false } -bridge-hub-common = { path = "../../../../../cumulus/parachains/runtimes/bridge-hubs/common", default-features = false } +bridge-hub-common = { path = "../../../../cumulus/parachains/runtimes/bridge-hubs/common", default-features = false } snowbridge-core = { path = "../../primitives/core", default-features = false, features = ["serde"] } snowbridge-outbound-queue-merkle-tree = { path = "merkle-tree", default-features = false } ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } [dev-dependencies] -pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } +pallet-message-queue = { path = "../../../../substrate/frame/message-queue", default-features = false } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } hex-literal = { version = "0.4.1" } [features] diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/README.md b/bridges/snowbridge/pallets/outbound-queue/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/README.md rename to bridges/snowbridge/pallets/outbound-queue/README.md diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml similarity index 74% rename from bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/Cargo.toml rename to bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml index 074b8031878e937d7dd7e70e6caa401ca9b6fbcd..c185d5af7062045f40946fcbd3c45cb62b932216 100644 --- a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml @@ -18,15 +18,15 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { version = "3.1.5", package = "parity-scale-codec", default-features = false, features = ["derive"] } scale-info = { version = "2.7.0", default-features = false, features = ["derive"] } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-runtime = { path = "../../../../../../substrate/primitives/runtime", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } [dev-dependencies] hex-literal = { version = "0.4.1" } env_logger = "0.9" hex = "0.4" array-bytes = "4.1" -sp-crypto-hashing = { path = "../../../../../../substrate/primitives/crypto/hashing" } +sp-crypto-hashing = { path = "../../../../../substrate/primitives/crypto/hashing" } [features] default = ["std"] diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/README.md b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/README.md rename to bridges/snowbridge/pallets/outbound-queue/merkle-tree/README.md diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/merkle-tree/src/lib.rs rename to bridges/snowbridge/pallets/outbound-queue/merkle-tree/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml similarity index 65% rename from bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/Cargo.toml rename to bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml index a1c465957e1df9843c8ff96d46ace84cf44dc85e..347b3bae493b7491790854be7a28f82386d2ee4b 100644 --- a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { version = "3.1.5", package = "parity-scale-codec", features = ["derive"], default-features = false } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../../substrate/primitives/std", default-features = false } -sp-api = { path = "../../../../../../substrate/primitives/api", default-features = false } -frame-support = { path = "../../../../../../substrate/frame/support", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../../polkadot/xcm", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } +frame-support = { path = "../../../../../substrate/frame/support", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } snowbridge-outbound-queue-merkle-tree = { path = "../merkle-tree", default-features = false } snowbridge-core = { path = "../../../primitives/core", default-features = false } diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/README.md b/bridges/snowbridge/pallets/outbound-queue/runtime-api/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/README.md rename to bridges/snowbridge/pallets/outbound-queue/runtime-api/README.md diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api/src/lib.rs rename to bridges/snowbridge/pallets/outbound-queue/runtime-api/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/api.rs b/bridges/snowbridge/pallets/outbound-queue/src/api.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/api.rs rename to bridges/snowbridge/pallets/outbound-queue/src/api.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/benchmarking.rs b/bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/benchmarking.rs rename to bridges/snowbridge/pallets/outbound-queue/src/benchmarking.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/lib.rs b/bridges/snowbridge/pallets/outbound-queue/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/lib.rs rename to bridges/snowbridge/pallets/outbound-queue/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/mock.rs b/bridges/snowbridge/pallets/outbound-queue/src/mock.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/mock.rs rename to bridges/snowbridge/pallets/outbound-queue/src/mock.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/process_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/process_message_impl.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/process_message_impl.rs rename to bridges/snowbridge/pallets/outbound-queue/src/process_message_impl.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/send_message_impl.rs b/bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/send_message_impl.rs rename to bridges/snowbridge/pallets/outbound-queue/src/send_message_impl.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/test.rs b/bridges/snowbridge/pallets/outbound-queue/src/test.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/test.rs rename to bridges/snowbridge/pallets/outbound-queue/src/test.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/types.rs b/bridges/snowbridge/pallets/outbound-queue/src/types.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/types.rs rename to bridges/snowbridge/pallets/outbound-queue/src/types.rs diff --git a/bridges/snowbridge/parachain/pallets/outbound-queue/src/weights.rs b/bridges/snowbridge/pallets/outbound-queue/src/weights.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/outbound-queue/src/weights.rs rename to bridges/snowbridge/pallets/outbound-queue/src/weights.rs diff --git a/bridges/snowbridge/parachain/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml similarity index 61% rename from bridges/snowbridge/parachain/pallets/system/Cargo.toml rename to bridges/snowbridge/pallets/system/Cargo.toml index da240344ba9a1f61d82b57f5e281125fdec1d49c..f6c642e7376f7f068af1fe3c2cf9b11a2c50f2cc 100644 --- a/bridges/snowbridge/parachain/pallets/system/Cargo.toml +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -19,19 +19,19 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -log = { version = "0.4.20", default-features = false } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +log = { workspace = true } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = false } snowbridge-core = { path = "../../primitives/core", default-features = false } @@ -39,10 +39,10 @@ snowbridge-core = { path = "../../primitives/core", default-features = false } [dev-dependencies] hex = "0.4.1" hex-literal = { version = "0.4.1" } -pallet-balances = { path = "../../../../../substrate/frame/balances" } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } -polkadot-primitives = { path = "../../../../../polkadot/primitives" } -pallet-message-queue = { path = "../../../../../substrate/frame/message-queue" } +pallet-balances = { path = "../../../../substrate/frame/balances" } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } +polkadot-primitives = { path = "../../../../polkadot/primitives" } +pallet-message-queue = { path = "../../../../substrate/frame/message-queue" } snowbridge-pallet-outbound-queue = { path = "../outbound-queue" } [features] diff --git a/bridges/snowbridge/parachain/pallets/system/README.md b/bridges/snowbridge/pallets/system/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/README.md rename to bridges/snowbridge/pallets/system/README.md diff --git a/bridges/snowbridge/parachain/pallets/system/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml similarity index 65% rename from bridges/snowbridge/parachain/pallets/system/runtime-api/Cargo.toml rename to bridges/snowbridge/pallets/system/runtime-api/Cargo.toml index 8f128e3a35de6d985bcb95f576d9e719660966bf..355d2d29147f3cd84ae013363db874c9b9739b8e 100644 --- a/bridges/snowbridge/parachain/pallets/system/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -sp-core = { path = "../../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../../substrate/primitives/std", default-features = false } -sp-api = { path = "../../../../../../substrate/primitives/api", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../../polkadot/xcm", default-features = false } +sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } snowbridge-core = { path = "../../../primitives/core", default-features = false } [features] diff --git a/bridges/snowbridge/parachain/pallets/system/runtime-api/README.md b/bridges/snowbridge/pallets/system/runtime-api/README.md similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/runtime-api/README.md rename to bridges/snowbridge/pallets/system/runtime-api/README.md diff --git a/bridges/snowbridge/parachain/pallets/system/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/system/runtime-api/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/runtime-api/src/lib.rs rename to bridges/snowbridge/pallets/system/runtime-api/src/lib.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/api.rs b/bridges/snowbridge/pallets/system/src/api.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/api.rs rename to bridges/snowbridge/pallets/system/src/api.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/benchmarking.rs b/bridges/snowbridge/pallets/system/src/benchmarking.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/benchmarking.rs rename to bridges/snowbridge/pallets/system/src/benchmarking.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/lib.rs b/bridges/snowbridge/pallets/system/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/lib.rs rename to bridges/snowbridge/pallets/system/src/lib.rs index b7f38fb753d31bd67acb78174e175f90fc711175..6e5ceb5e9b1d42796567c3da5e549b2af3cfd4de 100644 --- a/bridges/snowbridge/parachain/pallets/system/src/lib.rs +++ b/bridges/snowbridge/pallets/system/src/lib.rs @@ -37,8 +37,6 @@ //! `force_update_channel` and extrinsics to manage agents and channels for system parachains. #![cfg_attr(not(feature = "std"), no_std)] -pub use pallet::*; - #[cfg(test)] mod mock; @@ -79,6 +77,8 @@ use xcm_executor::traits::ConvertLocation; #[cfg(feature = "runtime-benchmarks")] use frame_support::traits::OriginTrait; +pub use pallet::*; + pub type BalanceOf = <::Token as Inspect<::AccountId>>::Balance; pub type AccountIdOf = ::AccountId; diff --git a/bridges/snowbridge/parachain/pallets/system/src/migration.rs b/bridges/snowbridge/pallets/system/src/migration.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/migration.rs rename to bridges/snowbridge/pallets/system/src/migration.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/mock.rs b/bridges/snowbridge/pallets/system/src/mock.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/mock.rs rename to bridges/snowbridge/pallets/system/src/mock.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/tests.rs b/bridges/snowbridge/pallets/system/src/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/tests.rs rename to bridges/snowbridge/pallets/system/src/tests.rs diff --git a/bridges/snowbridge/parachain/pallets/system/src/weights.rs b/bridges/snowbridge/pallets/system/src/weights.rs similarity index 100% rename from bridges/snowbridge/parachain/pallets/system/src/weights.rs rename to bridges/snowbridge/pallets/system/src/weights.rs diff --git a/bridges/snowbridge/parachain/LICENSE b/bridges/snowbridge/parachain/LICENSE deleted file mode 100644 index 261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64..0000000000000000000000000000000000000000 --- a/bridges/snowbridge/parachain/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/bridges/snowbridge/parachain/README.md b/bridges/snowbridge/parachain/README.md deleted file mode 100644 index a38910da3164e853f54b284f8d38795d4220aafe..0000000000000000000000000000000000000000 --- a/bridges/snowbridge/parachain/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Snowbridge · -[![codecov](https://codecov.io/gh/Snowfork/snowbridge/branch/main/graph/badge.svg?token=9hvgSws4rN)] -(https://codecov.io/gh/Snowfork/snowbridge) -![GitHub](https://img.shields.io/github/license/Snowfork/snowbridge) - -Snowbridge is a trustless bridge between Polkadot and Ethereum. For documentation, visit https://docs.snowbridge.network. - -## Components - -### Parachain - -Polkadot parachain and our pallets. See [parachain/README.md](https://github.com/Snowfork/snowbridge/blob/main/parachain/README.md). - -### Contracts - -Ethereum contracts and unit tests. See [contracts/README.md](https://github.com/Snowfork/snowbridge/blob/main/contracts/README.md) - -### Relayer - -Off-chain relayer services for relaying messages between Polkadot and Ethereum. See -[relayer/README.md](https://github.com/Snowfork/snowbridge/blob/main/relayer/README.md) - -### Local Testnet - -Scripts to provision a local testnet, running the above services to bridge between local deployments of Polkadot and -Ethereum. See [web/packages/test/README.md](https://github.com/Snowfork/snowbridge/blob/main/web/packages/test/README.md). - -### Smoke Tests - -Integration tests for our local testnet. See [smoketest/README.md](https://github.com/Snowfork/snowbridge/blob/main/smoketest/README.md). - -## Development - -We use the Nix package manager to provide a reproducible and maintainable developer environment. - -After [installing nix](https://nixos.org/download.html) Nix, enable [flakes](https://nixos.wiki/wiki/Flakes): - -```sh -mkdir -p ~/.config/nix -echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf -``` - -Then activate a developer shell in the root of our repo, where -[`flake.nix`](https://github.com/Snowfork/snowbridge/blob/main/flake.nix) is located: - -```sh -nix develop -``` - -Also make sure to run this initialization script once: -```sh -scripts/init.sh -``` - -### Support for code editors - -To ensure your code editor (such as VS Code) can execute tools in the nix shell, startup your editor within the -interactive shell. - -Example for VS Code: - -```sh -nix develop -code . -``` - -### Custom shells - -The developer shell is bash by default. To preserve your existing shell: - -```sh -nix develop --command $SHELL -``` - -### Automatic developer shells - -To automatically enter the developer shell whenever you open the project, install -[`direnv`](https://direnv.net/docs/installation.html) and use the template `.envrc`: - -```sh -cp .envrc.example .envrc -direnv allow -``` - -### Upgrading the Rust toolchain - -Sometimes we would like to upgrade rust toolchain. First update `parachain/rust-toolchain.toml` as required and then -update `flake.lock` running -```sh -nix flake lock --update-input rust-overlay -``` - -## Troubleshooting - -Check the contents of all `.envrc` files. - -Remove untracked files: -```sh -git clean -idx -``` - -Ensure that the current Rust toolchain is the one selected in `scripts/init.sh`. - -Ensure submodules are up-to-date: -```sh -git submodule update -``` - -Check untracked files & directories: -```sh -git clean -ndx | awk '{print $3}' -``` -After removing `node_modules` directories (eg. with `git clean above`), clear the pnpm cache: -```sh -pnpm store prune -``` - -Check Nix config in `~/.config/nix/nix.conf`. - -Run a pure developer shell (note that this removes access to your local tools): -```sh -nix develop -i --pure-eval -``` - -## Security - -The security policy and procedures can be found in SECURITY.md. diff --git a/bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml b/bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml deleted file mode 100644 index 4ec2f3b8620332641758c95f2c1c685e261cba42..0000000000000000000000000000000000000000 --- a/bridges/snowbridge/parachain/primitives/ethereum/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.wasm32-unknown-unknown] -runner = 'wasm-bindgen-test-runner' diff --git a/bridges/snowbridge/parachain/scripts/verify-pallets-build.sh b/bridges/snowbridge/parachain/scripts/verify-pallets-build.sh deleted file mode 100755 index a62f48c84d4fd34731c20365a20097e086aa2c99..0000000000000000000000000000000000000000 --- a/bridges/snowbridge/parachain/scripts/verify-pallets-build.sh +++ /dev/null @@ -1,116 +0,0 @@ -#!/bin/bash - -# A script to remove everything from snowbridge repository/subtree, except: -# -# - parachain -# - readme -# - license - -set -eu - -# show CLI help -function show_help() { - set +x - echo " " - echo Error: $1 - echo "Usage:" - echo " ./scripts/verify-pallets-build.sh Exit with code 0 if pallets code is well decoupled from the other code in the repo" - echo "Options:" - echo " --no-revert Leaves only runtime code on exit" - echo " --ignore-git-state Ignores git actual state" - exit 1 -} - -# parse CLI args -NO_REVERT= -IGNORE_GIT_STATE= -for i in "$@" -do - case $i in - --no-revert) - NO_REVERT=true - shift - ;; - --ignore-git-state) - IGNORE_GIT_STATE=true - shift - ;; - *) - show_help "Unknown option: $i" - ;; - esac -done - -# the script is able to work only on clean git copy, unless we want to ignore this check -[[ ! -z "${IGNORE_GIT_STATE}" ]] || [[ -z "$(git status --porcelain)" ]] || { echo >&2 "The git copy must be clean"; exit 1; } - -# let's avoid any restrictions on where this script can be called for - snowbridge repo may be -# plugged into any other repo folder. So the script (and other stuff that needs to be removed) -# may be located either in call dir, or one of it subdirs. -SNOWBRIDGE_FOLDER="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/../.." - -# remove everything we think is not required for our needs -rm -rf $SNOWBRIDGE_FOLDER/.cargo -rm -rf $SNOWBRIDGE_FOLDER/.github -rm -rf $SNOWBRIDGE_FOLDER/contracts -rm -rf $SNOWBRIDGE_FOLDER/codecov.yml -rm -rf $SNOWBRIDGE_FOLDER/docs -rm -rf $SNOWBRIDGE_FOLDER/hooks -rm -rf $SNOWBRIDGE_FOLDER/relayer -rm -rf $SNOWBRIDGE_FOLDER/scripts -rm -rf $SNOWBRIDGE_FOLDER/SECURITY.md -rm -rf $SNOWBRIDGE_FOLDER/smoketest -rm -rf $SNOWBRIDGE_FOLDER/web -rm -rf $SNOWBRIDGE_FOLDER/.envrc-example -rm -rf $SNOWBRIDGE_FOLDER/.gitbook.yaml -rm -rf $SNOWBRIDGE_FOLDER/.gitignore -rm -rf $SNOWBRIDGE_FOLDER/.gitmodules -rm -rf $SNOWBRIDGE_FOLDER/_typos.toml -rm -rf $SNOWBRIDGE_FOLDER/_codecov.yml -rm -rf $SNOWBRIDGE_FOLDER/flake.lock -rm -rf $SNOWBRIDGE_FOLDER/flake.nix -rm -rf $SNOWBRIDGE_FOLDER/go.work -rm -rf $SNOWBRIDGE_FOLDER/go.work.sum -rm -rf $SNOWBRIDGE_FOLDER/polkadot-sdk -rm -rf $SNOWBRIDGE_FOLDER/rust-toolchain.toml -rm -rf $SNOWBRIDGE_FOLDER/parachain/rustfmt.toml -rm -rf $SNOWBRIDGE_FOLDER/parachain/.gitignore -rm -rf $SNOWBRIDGE_FOLDER/parachain/templates -rm -rf $SNOWBRIDGE_FOLDER/parachain/.cargo -rm -rf $SNOWBRIDGE_FOLDER/parachain/.config -rm -rf $SNOWBRIDGE_FOLDER/parachain/pallets/ethereum-client/fuzz - -cd bridges/snowbridge/parachain - -# fix polkadot-sdk paths in Cargo.toml files -find "." -name 'Cargo.toml' | while read -r file; do - replace=$(printf '../../' ) - if [[ "$(uname)" = "Darwin" ]] || [[ "$(uname)" = *BSD ]]; then - sed -i '' "s|polkadot-sdk/|$replace|g" "$file" - else - sed -i "s|polkadot-sdk/|$replace|g" "$file" - fi -done - -# let's test if everything we need compiles -cargo check -p snowbridge-pallet-ethereum-client -cargo check -p snowbridge-pallet-ethereum-client --features runtime-benchmarks -cargo check -p snowbridge-pallet-ethereum-client --features try-runtime -cargo check -p snowbridge-pallet-inbound-queue -cargo check -p snowbridge-pallet-inbound-queue --features runtime-benchmarks -cargo check -p snowbridge-pallet-inbound-queue --features try-runtime -cargo check -p snowbridge-pallet-outbound-queue -cargo check -p snowbridge-pallet-outbound-queue --features runtime-benchmarks -cargo check -p snowbridge-pallet-outbound-queue --features try-runtime -cargo check -p snowbridge-pallet-system -cargo check -p snowbridge-pallet-system --features runtime-benchmarks -cargo check -p snowbridge-pallet-system --features try-runtime - -cd - - -# we're removing lock file after all checks are done. Otherwise we may use different -# Substrate/Polkadot/Cumulus commits and our checks will fail -rm -f $SNOWBRIDGE_FOLDER/parachain/Cargo.toml -rm -f $SNOWBRIDGE_FOLDER/parachain/Cargo.lock - -echo "OK" diff --git a/bridges/snowbridge/parachain/primitives/beacon/Cargo.toml b/bridges/snowbridge/primitives/beacon/Cargo.toml similarity index 68% rename from bridges/snowbridge/parachain/primitives/beacon/Cargo.toml rename to bridges/snowbridge/primitives/beacon/Cargo.toml index 3c2b3fcd065e3bce4e269b805426d55067537925..e021bb01071471b56d44f4a430c9ae5b7bdca57d 100644 --- a/bridges/snowbridge/parachain/primitives/beacon/Cargo.toml +++ b/bridges/snowbridge/primitives/beacon/Cargo.toml @@ -12,18 +12,18 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } hex = { version = "0.4", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } rlp = { version = "0.5", default-features = false } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } ssz_rs = { version = "0.9.0", default-features = false } ssz_rs_derive = { version = "0.9.0", default-features = false } diff --git a/bridges/snowbridge/parachain/primitives/beacon/README.md b/bridges/snowbridge/primitives/beacon/README.md similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/README.md rename to bridges/snowbridge/primitives/beacon/README.md diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/bits.rs b/bridges/snowbridge/primitives/beacon/src/bits.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/bits.rs rename to bridges/snowbridge/primitives/beacon/src/bits.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/bls.rs b/bridges/snowbridge/primitives/beacon/src/bls.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/bls.rs rename to bridges/snowbridge/primitives/beacon/src/bls.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/config.rs b/bridges/snowbridge/primitives/beacon/src/config.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/config.rs rename to bridges/snowbridge/primitives/beacon/src/config.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/lib.rs b/bridges/snowbridge/primitives/beacon/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/lib.rs rename to bridges/snowbridge/primitives/beacon/src/lib.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/merkle_proof.rs b/bridges/snowbridge/primitives/beacon/src/merkle_proof.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/merkle_proof.rs rename to bridges/snowbridge/primitives/beacon/src/merkle_proof.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/receipt.rs b/bridges/snowbridge/primitives/beacon/src/receipt.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/receipt.rs rename to bridges/snowbridge/primitives/beacon/src/receipt.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/serde_utils.rs b/bridges/snowbridge/primitives/beacon/src/serde_utils.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/serde_utils.rs rename to bridges/snowbridge/primitives/beacon/src/serde_utils.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/ssz.rs b/bridges/snowbridge/primitives/beacon/src/ssz.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/ssz.rs rename to bridges/snowbridge/primitives/beacon/src/ssz.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/types.rs b/bridges/snowbridge/primitives/beacon/src/types.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/types.rs rename to bridges/snowbridge/primitives/beacon/src/types.rs diff --git a/bridges/snowbridge/parachain/primitives/beacon/src/updates.rs b/bridges/snowbridge/primitives/beacon/src/updates.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/beacon/src/updates.rs rename to bridges/snowbridge/primitives/beacon/src/updates.rs diff --git a/bridges/snowbridge/parachain/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml similarity index 57% rename from bridges/snowbridge/parachain/primitives/core/Cargo.toml rename to bridges/snowbridge/primitives/core/Cargo.toml index 4e56495c1e6b6d5d5453aaf00f466f90fab1a49d..090c48c403fecf76b598abcc390ffa054a8281a3 100644 --- a/bridges/snowbridge/parachain/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -12,22 +12,22 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.195", optional = true, features = ["alloc", "derive"], default-features = false } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1" } -polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } +polkadot-parachain-primitives = { path = "../../../../polkadot/parachain", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-arithmetic = { path = "../../../../../substrate/primitives/arithmetic", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-arithmetic = { path = "../../../../substrate/primitives/arithmetic", default-features = false } snowbridge-beacon-primitives = { path = "../beacon", default-features = false } diff --git a/bridges/snowbridge/parachain/primitives/core/README.md b/bridges/snowbridge/primitives/core/README.md similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/README.md rename to bridges/snowbridge/primitives/core/README.md diff --git a/bridges/snowbridge/parachain/primitives/core/src/inbound.rs b/bridges/snowbridge/primitives/core/src/inbound.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/inbound.rs rename to bridges/snowbridge/primitives/core/src/inbound.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/lib.rs rename to bridges/snowbridge/primitives/core/src/lib.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/operating_mode.rs b/bridges/snowbridge/primitives/core/src/operating_mode.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/operating_mode.rs rename to bridges/snowbridge/primitives/core/src/operating_mode.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/outbound.rs b/bridges/snowbridge/primitives/core/src/outbound.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/outbound.rs rename to bridges/snowbridge/primitives/core/src/outbound.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/pricing.rs b/bridges/snowbridge/primitives/core/src/pricing.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/pricing.rs rename to bridges/snowbridge/primitives/core/src/pricing.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/ringbuffer.rs b/bridges/snowbridge/primitives/core/src/ringbuffer.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/ringbuffer.rs rename to bridges/snowbridge/primitives/core/src/ringbuffer.rs diff --git a/bridges/snowbridge/parachain/primitives/core/src/tests.rs b/bridges/snowbridge/primitives/core/src/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/src/tests.rs rename to bridges/snowbridge/primitives/core/src/tests.rs diff --git a/bridges/snowbridge/parachain/primitives/core/tests/fixtures/packet.scale b/bridges/snowbridge/primitives/core/tests/fixtures/packet.scale similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/tests/fixtures/packet.scale rename to bridges/snowbridge/primitives/core/tests/fixtures/packet.scale diff --git a/bridges/snowbridge/parachain/primitives/core/tests/mod.rs b/bridges/snowbridge/primitives/core/tests/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/core/tests/mod.rs rename to bridges/snowbridge/primitives/core/tests/mod.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/Cargo.toml b/bridges/snowbridge/primitives/ethereum/Cargo.toml similarity index 69% rename from bridges/snowbridge/parachain/primitives/ethereum/Cargo.toml rename to bridges/snowbridge/primitives/ethereum/Cargo.toml index c419f3f47f7398fc0fc3555ca7105ca31ee83d75..399139cef3816a2f840f4937a7449034aea9638d 100644 --- a/bridges/snowbridge/parachain/primitives/ethereum/Cargo.toml +++ b/bridges/snowbridge/primitives/ethereum/Cargo.toml @@ -12,8 +12,8 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.195", optional = true, features = ["derive"] } -serde-big-array = { version = "0.3.2", optional = true, features = ["const-generics"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +serde-big-array = { optional = true, features = ["const-generics"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } ethbloom = { version = "0.13.0", default-features = false } @@ -23,17 +23,17 @@ hex-literal = { version = "0.4.1", default-features = false } parity-bytes = { version = "0.1.2", default-features = false } rlp = { version = "0.5.2", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } ethabi = { package = "ethabi-decode", version = "1.0.0", default-features = false } [dev-dependencies] wasm-bindgen-test = "0.3.19" rand = "0.8.5" -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/parachain/primitives/ethereum/README.md b/bridges/snowbridge/primitives/ethereum/README.md similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/README.md rename to bridges/snowbridge/primitives/ethereum/README.md diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/header.rs b/bridges/snowbridge/primitives/ethereum/src/header.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/header.rs rename to bridges/snowbridge/primitives/ethereum/src/header.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/lib.rs b/bridges/snowbridge/primitives/ethereum/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/lib.rs rename to bridges/snowbridge/primitives/ethereum/src/lib.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/log.rs b/bridges/snowbridge/primitives/ethereum/src/log.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/log.rs rename to bridges/snowbridge/primitives/ethereum/src/log.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/mpt.rs b/bridges/snowbridge/primitives/ethereum/src/mpt.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/mpt.rs rename to bridges/snowbridge/primitives/ethereum/src/mpt.rs diff --git a/bridges/snowbridge/parachain/primitives/ethereum/src/receipt.rs b/bridges/snowbridge/primitives/ethereum/src/receipt.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/ethereum/src/receipt.rs rename to bridges/snowbridge/primitives/ethereum/src/receipt.rs diff --git a/bridges/snowbridge/parachain/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml similarity index 61% rename from bridges/snowbridge/parachain/primitives/router/Cargo.toml rename to bridges/snowbridge/primitives/router/Cargo.toml index dcb09499c55b4793cef27e9ecf610435ad2c8573..750a2a73e6372c138cb4420d5f06f26d35b90ab4 100644 --- a/bridges/snowbridge/parachain/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -12,21 +12,21 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } snowbridge-core = { path = "../core", default-features = false } diff --git a/bridges/snowbridge/parachain/primitives/router/README.md b/bridges/snowbridge/primitives/router/README.md similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/README.md rename to bridges/snowbridge/primitives/router/README.md diff --git a/bridges/snowbridge/parachain/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/inbound/mod.rs rename to bridges/snowbridge/primitives/router/src/inbound/mod.rs diff --git a/bridges/snowbridge/parachain/primitives/router/src/inbound/tests.rs b/bridges/snowbridge/primitives/router/src/inbound/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/inbound/tests.rs rename to bridges/snowbridge/primitives/router/src/inbound/tests.rs diff --git a/bridges/snowbridge/parachain/primitives/router/src/lib.rs b/bridges/snowbridge/primitives/router/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/lib.rs rename to bridges/snowbridge/primitives/router/src/lib.rs diff --git a/bridges/snowbridge/parachain/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/outbound/mod.rs rename to bridges/snowbridge/primitives/router/src/outbound/mod.rs diff --git a/bridges/snowbridge/parachain/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/primitives/router/src/outbound/tests.rs rename to bridges/snowbridge/primitives/router/src/outbound/tests.rs diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/Cargo.toml b/bridges/snowbridge/runtime/runtime-common/Cargo.toml similarity index 61% rename from bridges/snowbridge/parachain/runtime/runtime-common/Cargo.toml rename to bridges/snowbridge/runtime/runtime-common/Cargo.toml index 1fdb10beeb95af68de298a181f074cf248b266cc..d4c86f8aa750134df8dfbe22a277a872e77f9175 100644 --- a/bridges/snowbridge/parachain/runtime/runtime-common/Cargo.toml +++ b/bridges/snowbridge/runtime/runtime-common/Cargo.toml @@ -12,15 +12,15 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -log = { version = "0.4.20", default-features = false } +log = { workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-arithmetic = { path = "../../../../../substrate/primitives/arithmetic", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-arithmetic = { path = "../../../../substrate/primitives/arithmetic", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } snowbridge-core = { path = "../../primitives/core", default-features = false } diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/README.md b/bridges/snowbridge/runtime/runtime-common/README.md similarity index 100% rename from bridges/snowbridge/parachain/runtime/runtime-common/README.md rename to bridges/snowbridge/runtime/runtime-common/README.md diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/src/lib.rs b/bridges/snowbridge/runtime/runtime-common/src/lib.rs similarity index 100% rename from bridges/snowbridge/parachain/runtime/runtime-common/src/lib.rs rename to bridges/snowbridge/runtime/runtime-common/src/lib.rs diff --git a/bridges/snowbridge/parachain/runtime/runtime-common/src/tests.rs b/bridges/snowbridge/runtime/runtime-common/src/tests.rs similarity index 100% rename from bridges/snowbridge/parachain/runtime/runtime-common/src/tests.rs rename to bridges/snowbridge/runtime/runtime-common/src/tests.rs diff --git a/bridges/snowbridge/parachain/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml similarity index 50% rename from bridges/snowbridge/parachain/runtime/test-common/Cargo.toml rename to bridges/snowbridge/runtime/test-common/Cargo.toml index 8c5bb153c3bcf7c18992f10d78fefffcd465cfd2..ec4466b34265fd344e870f92f64eea014ad76b98 100644 --- a/bridges/snowbridge/parachain/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -13,68 +13,68 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } smallvec = "1.11.0" # Substrate -frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } -frame-executive = { path = "../../../../../substrate/frame/executive", default-features = false } -frame-support = { path = "../../../../../substrate/frame/support", default-features = false } -frame-system = { path = "../../../../../substrate/frame/system", default-features = false } -frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } -frame-system-rpc-runtime-api = { path = "../../../../../substrate/frame/system/rpc/runtime-api", default-features = false } -frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true } -pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false } -pallet-authorship = { path = "../../../../../substrate/frame/authorship", default-features = false } -pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } -pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } -pallet-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false } -pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } -pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } -pallet-transaction-payment = { path = "../../../../../substrate/frame/transaction-payment", default-features = false } -pallet-transaction-payment-rpc-runtime-api = { path = "../../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } -pallet-utility = { path = "../../../../../substrate/frame/utility", default-features = false } -sp-api = { path = "../../../../../substrate/primitives/api", default-features = false } -sp-block-builder = { path = "../../../../../substrate/primitives/block-builder", default-features = false } -sp-consensus-aura = { path = "../../../../../substrate/primitives/consensus/aura", default-features = false } -sp-core = { path = "../../../../../substrate/primitives/core", default-features = false } -sp-genesis-builder = { path = "../../../../../substrate/primitives/genesis-builder", default-features = false } -sp-inherents = { path = "../../../../../substrate/primitives/inherents", default-features = false } -sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } -sp-offchain = { path = "../../../../../substrate/primitives/offchain", default-features = false } -sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } -sp-session = { path = "../../../../../substrate/primitives/session", default-features = false } -sp-std = { path = "../../../../../substrate/primitives/std", default-features = false } -sp-storage = { path = "../../../../../substrate/primitives/storage", default-features = false } -sp-transaction-pool = { path = "../../../../../substrate/primitives/transaction-pool", default-features = false } -sp-version = { path = "../../../../../substrate/primitives/version", default-features = false } +frame-benchmarking = { path = "../../../../substrate/frame/benchmarking", default-features = false, optional = true } +frame-executive = { path = "../../../../substrate/frame/executive", default-features = false } +frame-support = { path = "../../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../../substrate/frame/system", default-features = false } +frame-system-benchmarking = { path = "../../../../substrate/frame/system/benchmarking", default-features = false, optional = true } +frame-system-rpc-runtime-api = { path = "../../../../substrate/frame/system/rpc/runtime-api", default-features = false } +frame-try-runtime = { path = "../../../../substrate/frame/try-runtime", default-features = false, optional = true } +pallet-aura = { path = "../../../../substrate/frame/aura", default-features = false } +pallet-authorship = { path = "../../../../substrate/frame/authorship", default-features = false } +pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } +pallet-session = { path = "../../../../substrate/frame/session", default-features = false } +pallet-multisig = { path = "../../../../substrate/frame/multisig", default-features = false } +pallet-message-queue = { path = "../../../../substrate/frame/message-queue", default-features = false } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-features = false } +pallet-transaction-payment = { path = "../../../../substrate/frame/transaction-payment", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { path = "../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } +pallet-utility = { path = "../../../../substrate/frame/utility", default-features = false } +sp-api = { path = "../../../../substrate/primitives/api", default-features = false } +sp-block-builder = { path = "../../../../substrate/primitives/block-builder", default-features = false } +sp-consensus-aura = { path = "../../../../substrate/primitives/consensus/aura", default-features = false } +sp-core = { path = "../../../../substrate/primitives/core", default-features = false } +sp-genesis-builder = { path = "../../../../substrate/primitives/genesis-builder", default-features = false } +sp-inherents = { path = "../../../../substrate/primitives/inherents", default-features = false } +sp-io = { path = "../../../../substrate/primitives/io", default-features = false } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } +sp-offchain = { path = "../../../../substrate/primitives/offchain", default-features = false } +sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } +sp-session = { path = "../../../../substrate/primitives/session", default-features = false } +sp-std = { path = "../../../../substrate/primitives/std", default-features = false } +sp-storage = { path = "../../../../substrate/primitives/storage", default-features = false } +sp-transaction-pool = { path = "../../../../substrate/primitives/transaction-pool", default-features = false } +sp-version = { path = "../../../../substrate/primitives/version", default-features = false } # Polkadot -pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } -pallet-xcm-benchmarks = { path = "../../../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false, optional = true } -polkadot-core-primitives = { path = "../../../../../polkadot/core-primitives", default-features = false } -polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } -polkadot-runtime-common = { path = "../../../../../polkadot/runtime/common", default-features = false } -xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } -xcm-builder = { package = "staging-xcm-builder", path = "../../../../../polkadot/xcm/xcm-builder", default-features = false } -xcm-executor = { package = "staging-xcm-executor", path = "../../../../../polkadot/xcm/xcm-executor", default-features = false } +pallet-xcm = { path = "../../../../polkadot/xcm/pallet-xcm", default-features = false } +pallet-xcm-benchmarks = { path = "../../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false, optional = true } +polkadot-core-primitives = { path = "../../../../polkadot/core-primitives", default-features = false } +polkadot-parachain-primitives = { path = "../../../../polkadot/parachain", default-features = false } +polkadot-runtime-common = { path = "../../../../polkadot/runtime/common", default-features = false } +xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +xcm-builder = { package = "staging-xcm-builder", path = "../../../../polkadot/xcm/xcm-builder", default-features = false } +xcm-executor = { package = "staging-xcm-executor", path = "../../../../polkadot/xcm/xcm-executor", default-features = false } # Cumulus -cumulus-pallet-aura-ext = { path = "../../../../../cumulus/pallets/aura-ext", default-features = false } -cumulus-pallet-parachain-system = { path = "../../../../../cumulus/pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } -cumulus-pallet-session-benchmarking = { path = "../../../../../cumulus/pallets/session-benchmarking", default-features = false } -cumulus-pallet-xcm = { path = "../../../../../cumulus/pallets/xcm", default-features = false } -cumulus-pallet-xcmp-queue = { path = "../../../../../cumulus/pallets/xcmp-queue", default-features = false, features = ["bridging"] } -cumulus-primitives-core = { path = "../../../../../cumulus/primitives/core", default-features = false } -cumulus-primitives-utility = { path = "../../../../../cumulus/primitives/utility", default-features = false } -pallet-collator-selection = { path = "../../../../../cumulus/pallets/collator-selection", default-features = false } -parachain-info = { package = "staging-parachain-info", path = "../../../../../cumulus/parachains/pallets/parachain-info", default-features = false } -parachains-common = { path = "../../../../../cumulus/parachains/common", default-features = false } -parachains-runtimes-test-utils = { path = "../../../../../cumulus/parachains/runtimes/test-utils", default-features = false } -assets-common = { path = "../../../../../cumulus/parachains/runtimes/assets/common", default-features = false } +cumulus-pallet-aura-ext = { path = "../../../../cumulus/pallets/aura-ext", default-features = false } +cumulus-pallet-parachain-system = { path = "../../../../cumulus/pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +cumulus-pallet-session-benchmarking = { path = "../../../../cumulus/pallets/session-benchmarking", default-features = false } +cumulus-pallet-xcm = { path = "../../../../cumulus/pallets/xcm", default-features = false } +cumulus-pallet-xcmp-queue = { path = "../../../../cumulus/pallets/xcmp-queue", default-features = false, features = ["bridging"] } +cumulus-primitives-core = { path = "../../../../cumulus/primitives/core", default-features = false } +cumulus-primitives-utility = { path = "../../../../cumulus/primitives/utility", default-features = false } +pallet-collator-selection = { path = "../../../../cumulus/pallets/collator-selection", default-features = false } +parachain-info = { package = "staging-parachain-info", path = "../../../../cumulus/parachains/pallets/parachain-info", default-features = false } +parachains-common = { path = "../../../../cumulus/parachains/common", default-features = false } +parachains-runtimes-test-utils = { path = "../../../../cumulus/parachains/runtimes/test-utils", default-features = false } +assets-common = { path = "../../../../cumulus/parachains/runtimes/assets/common", default-features = false } # Ethereum Bridge (Snowbridge) snowbridge-core = { path = "../../primitives/core", default-features = false } @@ -90,9 +90,9 @@ snowbridge-system-runtime-api = { path = "../../pallets/system/runtime-api", def [dev-dependencies] static_assertions = "1.1" -bridge-hub-test-utils = { path = "../../../../../cumulus/parachains/runtimes/bridge-hubs/test-utils" } -bridge-runtime-common = { path = "../../../../bin/runtime-common", features = ["integrity-test"] } -sp-keyring = { path = "../../../../../substrate/primitives/keyring" } +bridge-hub-test-utils = { path = "../../../../cumulus/parachains/runtimes/bridge-hubs/test-utils" } +bridge-runtime-common = { path = "../../../bin/runtime-common", features = ["integrity-test"] } +sp-keyring = { path = "../../../../substrate/primitives/keyring" } [features] default = ["std"] diff --git a/bridges/snowbridge/parachain/runtime/test-common/README.md b/bridges/snowbridge/runtime/test-common/README.md similarity index 100% rename from bridges/snowbridge/parachain/runtime/test-common/README.md rename to bridges/snowbridge/runtime/test-common/README.md diff --git a/bridges/snowbridge/parachain/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs similarity index 82% rename from bridges/snowbridge/parachain/runtime/test-common/src/lib.rs rename to bridges/snowbridge/runtime/test-common/src/lib.rs index 50e727f886dfe46182548bf1a7278d3f588fa236..29b0e738c182e3a9dd930fe8596fc5430470d5e0 100644 --- a/bridges/snowbridge/parachain/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -13,9 +13,9 @@ use parachains_runtimes_test_utils::{ }; use snowbridge_core::{ChannelId, ParaId}; use snowbridge_pallet_ethereum_client_fixtures::*; -use sp_core::H160; +use sp_core::{H160, U256}; use sp_keyring::AccountKeyring::*; -use sp_runtime::{traits::Header, AccountId32, SaturatedConversion, Saturating}; +use sp_runtime::{traits::Header, AccountId32, DigestItem, SaturatedConversion, Saturating}; use xcm::{ latest::prelude::*, v3::Error::{self, Barrier}, @@ -53,7 +53,8 @@ where + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + snowbridge_pallet_outbound_queue::Config, + + snowbridge_pallet_outbound_queue::Config + + pallet_timestamp::Config, XcmConfig: xcm_executor::Config, { let assethub_parachain_location = Location::new(1, Parachain(assethub_parachain_id)); @@ -125,7 +126,8 @@ pub fn send_transfer_token_message_success( + pallet_message_queue::Config + cumulus_pallet_parachain_system::Config + snowbridge_pallet_outbound_queue::Config - + snowbridge_pallet_system::Config, + + snowbridge_pallet_system::Config + + pallet_timestamp::Config, XcmConfig: xcm_executor::Config, ValidatorIdOf: From>, ::AccountId: From + AsRef<[u8]>, @@ -184,7 +186,7 @@ pub fn send_transfer_token_message_success( >::on_finalize(next_block_number); let included_head = >::finalize(); - let origin: ParaId = (assethub_parachain_id as u32).into(); + let origin: ParaId = assethub_parachain_id.into(); let channel_id: ChannelId = origin.into(); let nonce = snowbridge_pallet_outbound_queue::Nonce::::try_get(channel_id); @@ -193,12 +195,100 @@ pub fn send_transfer_token_message_success( let digest = included_head.digest(); - //let digest = frame_system::Pallet::::digest(); let digest_items = digest.logs(); assert!(digest_items.len() == 1 && digest_items[0].as_other().is_some()); }); } +pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works< + Runtime, + XcmConfig, + AllPalletsWithoutSystem, +>( + collator_session_key: CollatorSessionKeys, + runtime_para_id: u32, + assethub_parachain_id: u32, + weth_contract_address: H160, + destination_address: H160, + fee_amount: u128, + snowbridge_pallet_outbound_queue: Box< + dyn Fn(Vec) -> Option>, + >, +) where + Runtime: frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + pallet_message_queue::Config + + cumulus_pallet_parachain_system::Config + + snowbridge_pallet_outbound_queue::Config + + snowbridge_pallet_system::Config + + pallet_timestamp::Config, + XcmConfig: xcm_executor::Config, + AllPalletsWithoutSystem: + OnInitialize> + OnFinalize>, + ValidatorIdOf: From>, + ::AccountId: From + AsRef<[u8]>, +{ + ExtBuilder::::default() + .with_collators(collator_session_key.collators()) + .with_session_keys(collator_session_key.session_keys()) + .with_para_id(runtime_para_id.into()) + .with_tracing() + .build() + .execute_with(|| { + >::initialize( + runtime_para_id.into(), + assethub_parachain_id.into(), + ) + .unwrap(); + + // fund asset hub sovereign account enough so it can pay fees + initial_fund::(assethub_parachain_id, 5_000_000_000_000); + + let outcome = send_transfer_token_message::( + assethub_parachain_id, + weth_contract_address, + destination_address, + fee_amount, + ); + + assert_ok!(outcome.ensure_complete()); + + // check events + let mut events = >::events() + .into_iter() + .filter_map(|e| snowbridge_pallet_outbound_queue(e.event.encode())); + assert!(events.any(|e| matches!( + e, + snowbridge_pallet_outbound_queue::Event::MessageQueued { .. } + ))); + + let next_block_number: U256 = >::block_number() + .saturating_add(BlockNumberFor::::from(1u32)) + .into(); + + let included_head = + RuntimeHelper::::run_to_block_with_finalize( + next_block_number.as_u32(), + ); + let digest = included_head.digest(); + let digest_items = digest.logs(); + + let mut found_outbound_digest = false; + for digest_item in digest_items { + match digest_item { + DigestItem::Other(_) => found_outbound_digest = true, + _ => {}, + } + } + + assert_eq!(found_outbound_digest, true); + }); +} + pub fn send_unpaid_transfer_token_message( collator_session_key: CollatorSessionKeys, runtime_para_id: u32, @@ -213,7 +303,8 @@ pub fn send_unpaid_transfer_token_message( + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + snowbridge_pallet_outbound_queue::Config, + + snowbridge_pallet_outbound_queue::Config + + pallet_timestamp::Config, XcmConfig: xcm_executor::Config, ValidatorIdOf: From>, { @@ -301,7 +392,8 @@ pub fn send_transfer_token_message_failure( + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + snowbridge_pallet_outbound_queue::Config - + snowbridge_pallet_system::Config, + + snowbridge_pallet_system::Config + + pallet_timestamp::Config, XcmConfig: xcm_executor::Config, ValidatorIdOf: From>, { @@ -349,7 +441,8 @@ pub fn ethereum_extrinsic( + cumulus_pallet_parachain_system::Config + snowbridge_pallet_outbound_queue::Config + snowbridge_pallet_system::Config - + snowbridge_pallet_ethereum_client::Config, + + snowbridge_pallet_ethereum_client::Config + + pallet_timestamp::Config, ValidatorIdOf: From>, ::RuntimeCall: From>, @@ -430,7 +523,8 @@ pub fn ethereum_to_polkadot_message_extrinsics_work( + cumulus_pallet_parachain_system::Config + snowbridge_pallet_outbound_queue::Config + snowbridge_pallet_system::Config - + snowbridge_pallet_ethereum_client::Config, + + snowbridge_pallet_ethereum_client::Config + + pallet_timestamp::Config, ValidatorIdOf: From>, ::RuntimeCall: From>, diff --git a/bridges/snowbridge/parachain/scripts/benchmark.sh b/bridges/snowbridge/scripts/benchmark.sh similarity index 100% rename from bridges/snowbridge/parachain/scripts/benchmark.sh rename to bridges/snowbridge/scripts/benchmark.sh diff --git a/bridges/snowbridge/scripts/contribute-upstream.sh b/bridges/snowbridge/scripts/contribute-upstream.sh new file mode 100755 index 0000000000000000000000000000000000000000..8aa2d2a7035e2f213c0fc2090952b4b162739963 --- /dev/null +++ b/bridges/snowbridge/scripts/contribute-upstream.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# A script to cleanup the Snowfork fork of the polkadot-sdk to contribute it upstream back to parity/polkadot-sdk +# ./bridges/snowbridge/scripts/contribute-upstream.sh + +# show CLI help +function show_help() { + set +x + echo " " + echo Error: $1 + echo "Usage:" + echo " ./bridges/snowbridge/scripts/contribute-upstream.sh Exit with code 0 if pallets code is well decoupled from the other code in the repo" + exit 1 +} + +if [[ -z "$1" ]]; then + echo "Please provide a branch name you would like your upstream branch to be named" + exit 1 +fi + +branch_name=$1 + +set -eux + +# let's avoid any restrictions on where this script can be called for - snowbridge repo may be +# plugged into any other repo folder. So the script (and other stuff that needs to be removed) +# may be located either in call dir, or one of it subdirs. +SNOWBRIDGE_FOLDER="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )/../" + +# Get the current Git branch name +current_branch=$(git rev-parse --abbrev-ref HEAD) + +if [ "$current_branch" = "$branch_name" ] || git branch | grep -q "$branch_name"; then + echo "Already on requested branch or branch exists, not creating." +else + git branch "$branch_name" +fi + +git checkout "$branch_name" + +# remove everything we think is not required for our needs +rm -rf rust-toolchain.toml +rm -rf $SNOWBRIDGE_FOLDER/.cargo +rm -rf $SNOWBRIDGE_FOLDER/.github +rm -rf $SNOWBRIDGE_FOLDER/SECURITY.md +rm -rf $SNOWBRIDGE_FOLDER/.gitignore +rm -rf $SNOWBRIDGE_FOLDER/templates +rm -rf $SNOWBRIDGE_FOLDER/pallets/ethereum-client/fuzz + +pushd $SNOWBRIDGE_FOLDER + +# let's test if everything we need compiles +cargo check -p snowbridge-pallet-ethereum-client +cargo check -p snowbridge-pallet-ethereum-client --features runtime-benchmarks +cargo check -p snowbridge-pallet-ethereum-client --features try-runtime +cargo check -p snowbridge-pallet-inbound-queue +cargo check -p snowbridge-pallet-inbound-queue --features runtime-benchmarks +cargo check -p snowbridge-pallet-inbound-queue --features try-runtime +cargo check -p snowbridge-pallet-outbound-queue +cargo check -p snowbridge-pallet-outbound-queue --features runtime-benchmarks +cargo check -p snowbridge-pallet-outbound-queue --features try-runtime +cargo check -p snowbridge-pallet-system +cargo check -p snowbridge-pallet-system --features runtime-benchmarks +cargo check -p snowbridge-pallet-system --features try-runtime + +# we're removing lock file after all checks are done. Otherwise we may use different +# Substrate/Polkadot/Cumulus commits and our checks will fail +rm -f $SNOWBRIDGE_FOLDER/Cargo.toml +rm -f $SNOWBRIDGE_FOLDER/Cargo.lock + +popd + +# Replace Parity's CI files, that we have overwritten in our fork, to run our own CI +rm -rf .github +git remote -v | grep -w parity || git remote add parity https://github.com/paritytech/polkadot-sdk +git fetch parity master +git checkout parity/master -- .github +git add -- .github + +echo "OK" diff --git a/bridges/snowbridge/parachain/scripts/hexliteral.sh b/bridges/snowbridge/scripts/hexliteral.sh similarity index 100% rename from bridges/snowbridge/parachain/scripts/hexliteral.sh rename to bridges/snowbridge/scripts/hexliteral.sh diff --git a/bridges/snowbridge/parachain/scripts/init.sh b/bridges/snowbridge/scripts/init.sh similarity index 100% rename from bridges/snowbridge/parachain/scripts/init.sh rename to bridges/snowbridge/scripts/init.sh diff --git a/bridges/snowbridge/parachain/scripts/make-build-config.sh b/bridges/snowbridge/scripts/make-build-config.sh similarity index 100% rename from bridges/snowbridge/parachain/scripts/make-build-config.sh rename to bridges/snowbridge/scripts/make-build-config.sh diff --git a/bridges/zombienet/README.md b/bridges/testing/README.md similarity index 94% rename from bridges/zombienet/README.md rename to bridges/testing/README.md index b601154b624ce69ed921ea6c2453d17c4d37b6c8..bd467a410d013c363913a8e4b2be8ca7b184e2dc 100644 --- a/bridges/zombienet/README.md +++ b/bridges/testing/README.md @@ -23,7 +23,7 @@ To start those tests, you need to: - copy fresh `substrate-relay` binary, built in previous point, to the `~/local_bridge_testing/bin/substrate-relay`; -- change the `POLKADOT_SDK_FOLDER` and `ZOMBIENET_BINARY_PATH` (and ensure that the nearby variables +- change the `POLKADOT_SDK_PATH` and `ZOMBIENET_BINARY_PATH` (and ensure that the nearby variables have correct values) in the `./run-tests.sh`. After that, you could run tests with the `./run-tests.sh` command. Hopefully, it'll show the diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml similarity index 82% rename from cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml rename to bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml index 99a7d0035b511c57ccf5c10fa94165933c495ba9..52271f9442131923f8a758b16df7610e73813d15 100644 --- a/cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +++ b/bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml @@ -2,7 +2,7 @@ node_spawn_timeout = 240 [relaychain] -default_command = "{{POLKADOT_BINARY_PATH}}" +default_command = "{{POLKADOT_BINARY}}" default_args = [ "-lparachain=debug,xcm=trace" ] chain = "rococo-local" @@ -36,24 +36,22 @@ cumulus_based = true [[parachains.collators]] name = "bridge-hub-rococo-collator1" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8933 ws_port = 8943 args = [ - "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring" + "-lparachain=debug,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] # run bob as parachain collator [[parachains.collators]] name = "bridge-hub-rococo-collator2" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8934 ws_port = 8944 args = [ - "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring" + "-lparachain=trace,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] [[parachains]] @@ -65,14 +63,14 @@ cumulus_based = true name = "asset-hub-rococo-collator1" rpc_port = 9911 ws_port = 9910 - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] [[parachains.collators]] name = "asset-hub-rococo-collator2" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] diff --git a/cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml similarity index 84% rename from cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml rename to bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml index 1919d1c63f25f154e4676599afb8a2969598c10b..f2550bcc9959638b21ea78043cca3bc12d3d23ea 100644 --- a/cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +++ b/bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml @@ -2,7 +2,7 @@ node_spawn_timeout = 240 [relaychain] -default_command = "{{POLKADOT_BINARY_PATH}}" +default_command = "{{POLKADOT_BINARY}}" default_args = [ "-lparachain=debug,xcm=trace" ] chain = "westend-local" @@ -36,24 +36,22 @@ cumulus_based = true [[parachains.collators]] name = "bridge-hub-westend-collator1" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8935 ws_port = 8945 args = [ - "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring" + "-lparachain=debug,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] # run bob as parachain collator [[parachains.collators]] name = "bridge-hub-westend-collator2" validator = true - command = "{{POLKADOT_PARACHAIN_BINARY_PATH}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" rpc_port = 8936 ws_port = 8946 args = [ - "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace", - "--force-authoring" + "-lparachain=trace,runtime::mmr=info,substrate=info,runtime=info,runtime::bridge-hub=trace,runtime::bridge=trace,runtime::bridge-dispatch=trace,bridge=trace,runtime::bridge-messages=trace,xcm=trace" ] [[parachains]] @@ -65,14 +63,14 @@ cumulus_based = true name = "asset-hub-westend-collator1" rpc_port = 9011 ws_port = 9010 - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] [[parachains.collators]] name = "asset-hub-westend-collator2" - command = "{{POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND}}" + command = "{{POLKADOT_PARACHAIN_BINARY}}" args = [ "-lparachain=debug,xcm=trace,runtime::bridge-transfer=trace" ] diff --git a/cumulus/scripts/bridges_rococo_westend.sh b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh similarity index 99% rename from cumulus/scripts/bridges_rococo_westend.sh rename to bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh index 3b6f8e892858ad034a9db23a717b4290f9024bde..de5be2e0af8861008781ca3eab81f0422dc81e91 100755 --- a/cumulus/scripts/bridges_rococo_westend.sh +++ b/bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh @@ -1,7 +1,7 @@ #!/bin/bash # import common functions -source "$(dirname "$0")"/bridges_common.sh +source "$FRAMEWORK_PATH/utils/bridges.sh" # Expected sovereign accounts. # @@ -185,8 +185,8 @@ function run_relay() { case "$1" in run-relay) - init_ro_wnd init_wnd_ro + init_ro_wnd run_relay ;; init-asset-hub-rococo-local) diff --git a/bridges/testing/environments/rococo-westend/helper.sh b/bridges/testing/environments/rococo-westend/helper.sh new file mode 100755 index 0000000000000000000000000000000000000000..0a13ded213f5d3a0920cb466fc974c129e9ad79a --- /dev/null +++ b/bridges/testing/environments/rococo-westend/helper.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +$ENV_PATH/bridges_rococo_westend.sh "$@" diff --git a/bridges/testing/environments/rococo-westend/rococo-init.zndsl b/bridges/testing/environments/rococo-westend/rococo-init.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..c913e4db31f49184eb8214fda4d525c3594b358b --- /dev/null +++ b/bridges/testing/environments/rococo-westend/rococo-init.zndsl @@ -0,0 +1,8 @@ +Description: Check if the HRMP channel between Rococo BH and Rococo AH was opened successfully +Network: ./bridge_hub_rococo_local_network.toml +Creds: config + +# ensure that initialization has completed +asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1013" within 300 seconds + + diff --git a/bridges/testing/environments/rococo-westend/rococo.zndsl b/bridges/testing/environments/rococo-westend/rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..5b49c7c632fa4dd0ce77134858a2f697acbfff16 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/rococo.zndsl @@ -0,0 +1,7 @@ +Description: Check if the with-Westend GRANPDA pallet was initialized at Rococo BH +Network: ./bridge_hub_rococo_local_network.toml +Creds: config + +# relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds + diff --git a/bridges/testing/environments/rococo-westend/spawn.sh b/bridges/testing/environments/rococo-westend/spawn.sh new file mode 100755 index 0000000000000000000000000000000000000000..cbd0b1bc623ab77876ed5ce3beefd7ab72db2d37 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/spawn.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +set -e + +trap "trap - SIGTERM && kill -9 -$$" SIGINT SIGTERM EXIT + +source "$FRAMEWORK_PATH/utils/zombienet.sh" + +# whether to init the chains (open HRMP channels, set XCM version, create reserve assets, etc) +init=0 +start_relayer=0 +while [ $# -ne 0 ] +do + arg="$1" + case "$arg" in + --init) + init=1 + ;; + --start-relayer) + start_relayer=1 + ;; + esac + shift +done + +logs_dir=$TEST_DIR/logs +helper_script="${BASH_SOURCE%/*}/helper.sh" + +rococo_def=${BASH_SOURCE%/*}/bridge_hub_rococo_local_network.toml +start_zombienet $TEST_DIR $rococo_def rococo_dir rococo_pid +echo + +westend_def=${BASH_SOURCE%/*}/bridge_hub_westend_local_network.toml +start_zombienet $TEST_DIR $westend_def westend_dir westend_pid +echo + +if [[ $init -eq 1 ]]; then + rococo_init_log=$logs_dir/rococo-init.log + echo -e "Setting up the rococo side of the bridge. Logs available at: $rococo_init_log\n" + + westend_init_log=$logs_dir/westend-init.log + echo -e "Setting up the westend side of the bridge. Logs available at: $westend_init_log\n" + + $helper_script init-asset-hub-rococo-local >> $rococo_init_log 2>&1 & + rococo_init_pid=$! + $helper_script init-asset-hub-westend-local >> $westend_init_log 2>&1 & + westend_init_pid=$! + wait -n $rococo_init_pid $westend_init_pid + + + $helper_script init-bridge-hub-rococo-local >> $rococo_init_log 2>&1 & + rococo_init_pid=$! + $helper_script init-bridge-hub-westend-local >> $westend_init_log 2>&1 & + westend_init_pid=$! + wait -n $rococo_init_pid $westend_init_pid + + run_zndsl ${BASH_SOURCE%/*}/rococo-init.zndsl $rococo_dir + run_zndsl ${BASH_SOURCE%/*}/westend-init.zndsl $westend_dir +fi + +if [[ $start_relayer -eq 1 ]]; then + ${BASH_SOURCE%/*}/start_relayer.sh $rococo_dir $westend_dir relayer_pid +fi + +echo $rococo_dir > $TEST_DIR/rococo.env +echo $westend_dir > $TEST_DIR/westend.env +echo + +wait -n $rococo_pid $westend_pid $relayer_pid +kill -9 -$$ diff --git a/bridges/testing/environments/rococo-westend/start_relayer.sh b/bridges/testing/environments/rococo-westend/start_relayer.sh new file mode 100755 index 0000000000000000000000000000000000000000..7ddd312d395aa8733d2afea59277b48721c8a36b --- /dev/null +++ b/bridges/testing/environments/rococo-westend/start_relayer.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +source "$FRAMEWORK_PATH/utils/common.sh" +source "$FRAMEWORK_PATH/utils/zombienet.sh" + +rococo_dir=$1 +westend_dir=$2 +__relayer_pid=$3 + +logs_dir=$TEST_DIR/logs +helper_script="${BASH_SOURCE%/*}/helper.sh" + +relayer_log=$logs_dir/relayer.log +echo -e "Starting rococo-westend relayer. Logs available at: $relayer_log\n" +start_background_process "$helper_script run-relay" $relayer_log relayer_pid + +run_zndsl ${BASH_SOURCE%/*}/rococo.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/westend.zndsl $westend_dir + +eval $__relayer_pid="'$relayer_pid'" + diff --git a/bridges/testing/environments/rococo-westend/westend-init.zndsl b/bridges/testing/environments/rococo-westend/westend-init.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..0f5428eed3b01c042f8aad3b3df51c3a800a9b72 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/westend-init.zndsl @@ -0,0 +1,7 @@ +Description: Check if the HRMP channel between Westend BH and Westend AH was opened successfully +Network: ./bridge_hub_westend_local_network.toml +Creds: config + +# ensure that initialization has completed +asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds + diff --git a/bridges/testing/environments/rococo-westend/westend.zndsl b/bridges/testing/environments/rococo-westend/westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..07968838852f7c0a00131db3080c460c07d08206 --- /dev/null +++ b/bridges/testing/environments/rococo-westend/westend.zndsl @@ -0,0 +1,6 @@ +Description: Check if the with-Rococo GRANPDA pallet was initialized at Westend BH +Network: ./bridge_hub_westend_local_network.toml +Creds: config + +# relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds diff --git a/bridges/zombienet/helpers/best-finalized-header-at-bridged-chain.js b/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js similarity index 94% rename from bridges/zombienet/helpers/best-finalized-header-at-bridged-chain.js rename to bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js index f7e1eefc84b3fa3e799d7111608cfc39783f5e21..af4f18aee9b2710612ed142c50b28caf8313326d 100644 --- a/bridges/zombienet/helpers/best-finalized-header-at-bridged-chain.js +++ b/bridges/testing/framework/js-helpers/best-finalized-header-at-bridged-chain.js @@ -18,7 +18,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js b/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js new file mode 100644 index 0000000000000000000000000000000000000000..bcce3b3a303f55a16e766c6558878650ed03ab80 --- /dev/null +++ b/bridges/testing/framework/js-helpers/chains/rococo-at-westend.js @@ -0,0 +1,6 @@ +module.exports = { + grandpaPalletName: "bridgeRococoGrandpa", + parachainsPalletName: "bridgeRococoParachains", + messagesPalletName: "bridgeRococoMessages", + bridgedBridgeHubParaId: 1013, +} diff --git a/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js b/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js new file mode 100644 index 0000000000000000000000000000000000000000..6a15b64a23b7c28f2b66a6491caebafc4c93dff5 --- /dev/null +++ b/bridges/testing/framework/js-helpers/chains/westend-at-rococo.js @@ -0,0 +1,6 @@ +module.exports = { + grandpaPalletName: "bridgeWestendGrandpa", + parachainsPalletName: "bridgeWestendParachains", + messagesPalletName: "bridgeWestendMessages", + bridgedBridgeHubParaId: 1002, +} diff --git a/bridges/zombienet/helpers/native-assets-balance-increased.js b/bridges/testing/framework/js-helpers/native-assets-balance-increased.js similarity index 90% rename from bridges/zombienet/helpers/native-assets-balance-increased.js rename to bridges/testing/framework/js-helpers/native-assets-balance-increased.js index 9ee1a769e9f2807ed7b73ca9c6aa4b89d5c135f9..a35c753d97326d7b200c33844e9c5b8be22dfebc 100644 --- a/bridges/zombienet/helpers/native-assets-balance-increased.js +++ b/bridges/testing/framework/js-helpers/native-assets-balance-increased.js @@ -13,7 +13,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js similarity index 88% rename from bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js rename to bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js index 3a3432cfaf38da93f3ea0e65657f266b66f84d74..979179245ebe9f5b250efca6f2e6199ef0ac86d7 100644 --- a/bridges/zombienet/helpers/only-mandatory-headers-synced-when-idle.js +++ b/bridges/testing/framework/js-helpers/only-mandatory-headers-synced-when-idle.js @@ -10,7 +10,7 @@ async function run(nodeName, networkInfo, args) { // start listening to new blocks let totalGrandpaHeaders = 0; - let totalParachainHeaders = 0; + let initialParachainHeaderImported = false; api.rpc.chain.subscribeNewHeads(async function (header) { const apiAtParent = await api.at(header.parentHash); const apiAtCurrent = await api.at(header.hash); @@ -22,7 +22,7 @@ async function run(nodeName, networkInfo, args) { apiAtCurrent, currentEvents, ); - totalParachainHeaders += await utils.ensureOnlyInitialParachainHeaderImported( + initialParachainHeaderImported = await utils.ensureOnlyInitialParachainHeaderImported( bridgedChain, apiAtParent, apiAtCurrent, @@ -36,7 +36,7 @@ async function run(nodeName, networkInfo, args) { if (totalGrandpaHeaders == 0) { throw new Error("No bridged relay chain headers imported"); } - if (totalParachainHeaders == 0) { + if (!initialParachainHeaderImported) { throw new Error("No bridged parachain headers imported"); } } diff --git a/bridges/zombienet/helpers/only-required-headers-synced-when-idle.js b/bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js similarity index 100% rename from bridges/zombienet/helpers/only-required-headers-synced-when-idle.js rename to bridges/testing/framework/js-helpers/only-required-headers-synced-when-idle.js diff --git a/bridges/zombienet/helpers/relayer-rewards.js b/bridges/testing/framework/js-helpers/relayer-rewards.js similarity index 93% rename from bridges/zombienet/helpers/relayer-rewards.js rename to bridges/testing/framework/js-helpers/relayer-rewards.js index a5f567db797722e04d3bfae90745a728ff1abdff..5347c649604fc209042725c9cf269c9d3ca0290f 100644 --- a/bridges/zombienet/helpers/relayer-rewards.js +++ b/bridges/testing/framework/js-helpers/relayer-rewards.js @@ -21,7 +21,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/bridges/zombienet/helpers/utils.js b/bridges/testing/framework/js-helpers/utils.js similarity index 98% rename from bridges/zombienet/helpers/utils.js rename to bridges/testing/framework/js-helpers/utils.js index 5a5542b56dfc215a082fc6fbb8c1b9aa018de83e..f6e9f5623b47b3cb3c642245e86654ae9f65358a 100644 --- a/bridges/zombienet/helpers/utils.js +++ b/bridges/testing/framework/js-helpers/utils.js @@ -98,6 +98,6 @@ module.exports = { throw new Error("Unexpected parachain header import: " + newParachainHeaders + " / " + maxNewParachainHeaders); } - return newParachainHeaders; + return hasBestBridgedParachainHeader; }, } diff --git a/bridges/zombienet/helpers/wait-hrmp-channel-opened.js b/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js similarity index 91% rename from bridges/zombienet/helpers/wait-hrmp-channel-opened.js rename to bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js index e700cab1d7481d77631e55492e4b0032f4382028..765d48cc49848ab7a4389f6e0d9b9b3b8cb38f2b 100644 --- a/bridges/zombienet/helpers/wait-hrmp-channel-opened.js +++ b/bridges/testing/framework/js-helpers/wait-hrmp-channel-opened.js @@ -15,7 +15,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/bridges/zombienet/helpers/wrapped-assets-balance.js b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js similarity index 93% rename from bridges/zombienet/helpers/wrapped-assets-balance.js rename to bridges/testing/framework/js-helpers/wrapped-assets-balance.js index bb3cea8858a850e551ba0380b1557ccad0761717..27287118547f702b3e94eb635f9e3855d1cab535 100644 --- a/bridges/zombienet/helpers/wrapped-assets-balance.js +++ b/bridges/testing/framework/js-helpers/wrapped-assets-balance.js @@ -19,7 +19,7 @@ async function run(nodeName, networkInfo, args) { } // else sleep and retry - await new Promise((resolve) => setTimeout(resolve, 12000)); + await new Promise((resolve) => setTimeout(resolve, 6000)); } } diff --git a/cumulus/scripts/bridges_common.sh b/bridges/testing/framework/utils/bridges.sh similarity index 97% rename from cumulus/scripts/bridges_common.sh rename to bridges/testing/framework/utils/bridges.sh index 029d4cd4ff74a5c88165913a48b2b369c0f185b8..7c8399461584a85e4e8eedf5f347d9d74725f1c9 100755 --- a/cumulus/scripts/bridges_common.sh +++ b/bridges/testing/framework/utils/bridges.sh @@ -2,7 +2,7 @@ function relayer_path() { local default_path=~/local_bridge_testing/bin/substrate-relay - local path="${SUBSTRATE_RELAY_PATH:-$default_path}" + local path="${SUBSTRATE_RELAY_BINARY:-$default_path}" echo "$path" } @@ -41,8 +41,8 @@ function ensure_polkadot_js_api() { echo "" echo "" echo "-------------------" - echo "Installing (nodejs) sub module: $(dirname "$0")/generate_hex_encoded_call" - pushd $(dirname "$0")/generate_hex_encoded_call + echo "Installing (nodejs) sub module: ${BASH_SOURCE%/*}/generate_hex_encoded_call" + pushd ${BASH_SOURCE%/*}/generate_hex_encoded_call npm install popd fi @@ -65,7 +65,7 @@ function generate_hex_encoded_call_data() { shift echo "Input params: $@" - node $(dirname "$0")/generate_hex_encoded_call "$type" "$endpoint" "$output" "$@" + node ${BASH_SOURCE%/*}/../utils/generate_hex_encoded_call "$type" "$endpoint" "$output" "$@" local retVal=$? if [ $type != "check" ]; then diff --git a/bridges/testing/framework/utils/common.sh b/bridges/testing/framework/utils/common.sh new file mode 100644 index 0000000000000000000000000000000000000000..06f41320be1353720fccc76b7b76e69ba56a3b94 --- /dev/null +++ b/bridges/testing/framework/utils/common.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +function start_background_process() { + local command=$1 + local log_file=$2 + local __pid=$3 + + $command > $log_file 2>&1 & + eval $__pid="'$!'" +} + +function wait_for_process_file() { + local pid=$1 + local file=$2 + local timeout=$3 + local __found=$4 + + local time=0 + until [ -e $file ]; do + if ! kill -0 $pid; then + echo "Process finished unsuccessfully" + return + fi + if (( time++ >= timeout )); then + echo "Timeout waiting for file $file: $timeout seconds" + eval $__found=0 + return + fi + sleep 1 + done + + echo "File $file found after $time seconds" + eval $__found=1 +} + +function ensure_process_file() { + local pid=$1 + local file=$2 + local timeout=$3 + + wait_for_process_file $pid $file $timeout file_found + if [ "$file_found" != "1" ]; then + exit 1 + fi +} diff --git a/cumulus/scripts/generate_hex_encoded_call/index.js b/bridges/testing/framework/utils/generate_hex_encoded_call/index.js similarity index 100% rename from cumulus/scripts/generate_hex_encoded_call/index.js rename to bridges/testing/framework/utils/generate_hex_encoded_call/index.js diff --git a/cumulus/scripts/generate_hex_encoded_call/package-lock.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json similarity index 100% rename from cumulus/scripts/generate_hex_encoded_call/package-lock.json rename to bridges/testing/framework/utils/generate_hex_encoded_call/package-lock.json diff --git a/cumulus/scripts/generate_hex_encoded_call/package.json b/bridges/testing/framework/utils/generate_hex_encoded_call/package.json similarity index 100% rename from cumulus/scripts/generate_hex_encoded_call/package.json rename to bridges/testing/framework/utils/generate_hex_encoded_call/package.json diff --git a/bridges/testing/framework/utils/zombienet.sh b/bridges/testing/framework/utils/zombienet.sh new file mode 100644 index 0000000000000000000000000000000000000000..bbcd1a30620252d8740473c3924e0988e5bff4d6 --- /dev/null +++ b/bridges/testing/framework/utils/zombienet.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +source "${BASH_SOURCE%/*}/common.sh" + +function start_zombienet() { + local test_dir=$1 + local definition_path=$2 + local __zombienet_dir=$3 + local __zombienet_pid=$4 + + local zombienet_name=`basename $definition_path .toml` + local zombienet_dir=$test_dir/$zombienet_name + eval $__zombienet_dir="'$zombienet_dir'" + mkdir -p $zombienet_dir + rm -rf $zombienet_dir + + local logs_dir=$test_dir/logs + mkdir -p $logs_dir + local zombienet_log=$logs_dir/$zombienet_name.log + + echo "Starting $zombienet_name zombienet. Logs available at: $zombienet_log" + start_background_process \ + "$ZOMBIENET_BINARY spawn --dir $zombienet_dir --provider native $definition_path" \ + "$zombienet_log" zombienet_pid + + ensure_process_file $zombienet_pid "$zombienet_dir/zombie.json" 180 + echo "$zombienet_name zombienet started successfully" + + eval $__zombienet_pid="'$zombienet_pid'" +} + +function run_zndsl() { + local zndsl_file=$1 + local zombienet_dir=$2 + + echo "Running $zndsl_file." + $ZOMBIENET_BINARY test --dir $zombienet_dir --provider native $zndsl_file $zombienet_dir/zombie.json + echo +} diff --git a/bridges/testing/run-new-test.sh b/bridges/testing/run-new-test.sh new file mode 100755 index 0000000000000000000000000000000000000000..7c84a69aa47de84439091cb7b908233d02238175 --- /dev/null +++ b/bridges/testing/run-new-test.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -e + +trap 'kill -9 -$$ || echo "Environment already teared down"' SIGINT SIGTERM EXIT + +test=$1 +shift + +# whether to use paths for zombienet+bridges tests container or for local testing +ZOMBIENET_DOCKER_PATHS=0 +while [ $# -ne 0 ] +do + arg="$1" + case "$arg" in + --docker) + ZOMBIENET_DOCKER_PATHS=1 + ;; + esac + shift +done + +export POLKADOT_SDK_PATH=`realpath ${BASH_SOURCE%/*}/../..` +export FRAMEWORK_PATH=`realpath ${BASH_SOURCE%/*}/framework` + +# set path to binaries +if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then + # otherwise zombienet uses some hardcoded paths + unset RUN_IN_CONTAINER + unset ZOMBIENET_IMAGE + + export POLKADOT_BINARY=/usr/local/bin/polkadot + export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain + + export ZOMBIENET_BINARY=/usr/local/bin/zombie + export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay +else + export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot + export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain + + export ZOMBIENET_BINARY=~/local_bridge_testing/bin/zombienet-linux-x64 + export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay +fi + +export TEST_DIR=`mktemp -d /tmp/bridges-tests-run-XXXXX` +echo -e "Test folder: $TEST_DIR\n" + +${BASH_SOURCE%/*}/tests/$test/run.sh diff --git a/bridges/zombienet/run-tests.sh b/bridges/testing/run-tests.sh similarity index 77% rename from bridges/zombienet/run-tests.sh rename to bridges/testing/run-tests.sh index cf3b529e6a9d9823f875938d8603b363c6079136..6149d9912653c79968a0229759c8f1bf46f68a9f 100755 --- a/bridges/zombienet/run-tests.sh +++ b/bridges/testing/run-tests.sh @@ -27,34 +27,27 @@ done # assuming that we'll be using native provide && all processes will be executing locally # (we need absolute paths here, because they're used when scripts are called by zombienet from tmp folders) -export POLKADOT_SDK_FOLDER=`realpath $(dirname "$0")/../..` -export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_FOLDER/bridges/zombienet/tests +export POLKADOT_SDK_PATH=`realpath $(dirname "$0")/../..` +export BRIDGE_TESTS_FOLDER=$POLKADOT_SDK_PATH/bridges/testing/tests # set pathc to binaries if [ "$ZOMBIENET_DOCKER_PATHS" -eq 1 ]; then - export POLKADOT_BINARY_PATH=/usr/local/bin/polkadot - export POLKADOT_PARACHAIN_BINARY_PATH=/usr/local/bin/polkadot-parachain - export POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO=/usr/local/bin/polkadot-parachain - export POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND=/usr/local/bin/polkadot-parachain + export POLKADOT_BINARY=/usr/local/bin/polkadot + export POLKADOT_PARACHAIN_BINARY=/usr/local/bin/polkadot-parachain - export SUBSTRATE_RELAY_PATH=/usr/local/bin/substrate-relay + export SUBSTRATE_RELAY_BINARY=/usr/local/bin/substrate-relay export ZOMBIENET_BINARY_PATH=/usr/local/bin/zombie else - export POLKADOT_BINARY_PATH=$POLKADOT_SDK_FOLDER/target/release/polkadot - export POLKADOT_PARACHAIN_BINARY_PATH=$POLKADOT_SDK_FOLDER/target/release/polkadot-parachain - export POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO=$POLKADOT_PARACHAIN_BINARY_PATH - export POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND=$POLKADOT_PARACHAIN_BINARY_PATH + export POLKADOT_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot + export POLKADOT_PARACHAIN_BINARY=$POLKADOT_SDK_PATH/target/release/polkadot-parachain - export SUBSTRATE_RELAY_PATH=~/local_bridge_testing/bin/substrate-relay + export SUBSTRATE_RELAY_BINARY=~/local_bridge_testing/bin/substrate-relay export ZOMBIENET_BINARY_PATH=~/local_bridge_testing/bin/zombienet-linux fi # check if `wait` supports -p flag if [ `printf "$BASH_VERSION\n5.1" | sort -V | head -n 1` = "5.1" ]; then IS_BASH_5_1=1; else IS_BASH_5_1=0; fi -# check if `wait` supports -p flag -if [ `printf "$BASH_VERSION\n5.1" | sort -V | head -n 1` = "5.1" ]; then IS_BASH_5_1=1; else IS_BASH_5_1=0; fi - # bridge configuration export LANE_ID="00000002" diff --git a/bridges/zombienet/scripts/invoke-script.sh b/bridges/testing/scripts/invoke-script.sh similarity index 62% rename from bridges/zombienet/scripts/invoke-script.sh rename to bridges/testing/scripts/invoke-script.sh index 835b4fe500f01ea2968bcb8bff538491ec7149bc..cd0557b071bbadc41e056a2e50c9f1aa0b677312 100755 --- a/bridges/zombienet/scripts/invoke-script.sh +++ b/bridges/testing/scripts/invoke-script.sh @@ -2,6 +2,6 @@ INVOKE_LOG=`mktemp -p $TEST_FOLDER invoke.XXXXX` -pushd $POLKADOT_SDK_FOLDER/cumulus/scripts +pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend ./bridges_rococo_westend.sh $1 >$INVOKE_LOG 2>&1 popd diff --git a/bridges/zombienet/scripts/start-relayer.sh b/bridges/testing/scripts/start-relayer.sh similarity index 63% rename from bridges/zombienet/scripts/start-relayer.sh rename to bridges/testing/scripts/start-relayer.sh index 2f72b5ee556bcc8a89b2de4c5d3c53db8ac072b1..38ea62fad524486c40cf88943c48a2e4df4b86e8 100755 --- a/bridges/zombienet/scripts/start-relayer.sh +++ b/bridges/testing/scripts/start-relayer.sh @@ -2,6 +2,6 @@ RELAY_LOG=`mktemp -p $TEST_FOLDER relay.XXXXX` -pushd $POLKADOT_SDK_FOLDER/cumulus/scripts +pushd $POLKADOT_SDK_PATH/bridges/testing/environments/rococo-westend ./bridges_rococo_westend.sh run-relay >$RELAY_LOG 2>&1& popd diff --git a/bridges/zombienet/scripts/sync-exit.sh b/bridges/testing/scripts/sync-exit.sh similarity index 100% rename from bridges/zombienet/scripts/sync-exit.sh rename to bridges/testing/scripts/sync-exit.sh diff --git a/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..4725362ae826ec6d8313ec45c0ce21f70f244ca6 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/roc-reaches-westend.zndsl @@ -0,0 +1,12 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# send ROC to //Alice from Rococo AH to Westend AH +asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 120 seconds + +# check that //Alice received the ROC on Westend AH +asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 300 seconds + +# check that the relayer //Charlie is rewarded by Westend AH +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 30 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/run.sh b/bridges/testing/tests/0001-asset-transfer/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..a7bb122919b40187c49e89c489d2271d646bff40 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/run.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e + +source "${BASH_SOURCE%/*}/../../framework/utils/common.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh" + +export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend` + +$ENV_PATH/spawn.sh --init --start-relayer & +env_pid=$! + +ensure_process_file $env_pid $TEST_DIR/rococo.env 600 +rococo_dir=`cat $TEST_DIR/rococo.env` +echo + +ensure_process_file $env_pid $TEST_DIR/westend.env 300 +westend_dir=`cat $TEST_DIR/westend.env` +echo + +run_zndsl ${BASH_SOURCE%/*}/roc-reaches-westend.zndsl $westend_dir +run_zndsl ${BASH_SOURCE%/*}/wnd-reaches-rococo.zndsl $rococo_dir + +run_zndsl ${BASH_SOURCE%/*}/wroc-reaches-rococo.zndsl $rococo_dir +run_zndsl ${BASH_SOURCE%/*}/wwnd-reaches-westend.zndsl $westend_dir diff --git a/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..77267239c3b12bb1c3caf88f9bb01bfd98f2c121 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/wnd-reaches-rococo.zndsl @@ -0,0 +1,12 @@ +Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back +Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml +Creds: config + +# send WND to //Alice from Westend AH to Rococo AH +asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 120 seconds + +# check that //Alice received the WND on Rococo AH +asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 300 seconds + +# check that the relayer //Charlie is rewarded by Rococo AH +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 30 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..f72b76e6026b6a0a24fa16fc4d08f79ba07f5e60 --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/wroc-reaches-rococo.zndsl @@ -0,0 +1,10 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# send wROC back to Alice from Westend AH to Rococo AH +asset-hub-rococo-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 120 seconds + +# check that //Alice received the wROC on Rococo AH +# (we wait until //Alice account increases here - there are no other transactions that may increase it) +asset-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds diff --git a/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..9d893c8d90a80df97b33b7ff16297700c7a8253a --- /dev/null +++ b/bridges/testing/tests/0001-asset-transfer/wwnd-reaches-westend.zndsl @@ -0,0 +1,10 @@ +Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# send wWND back to Alice from Rococo AH to Westend AH +asset-hub-westend-collator1: run {{ENV_PATH}}/helper.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 120 seconds + +# check that //Alice received the wWND on Westend AH +# (we wait until //Alice account increases here - there are no other transactions that may increase it) +asset-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 300 seconds diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..6e381f5377329430c0d7a8723f9ea9081556bfeb --- /dev/null +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/rococo-to-westend.zndsl @@ -0,0 +1,8 @@ +Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH. +Network: {{ENV_PATH}}/bridge_hub_westend_local_network.toml +Creds: config + +# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were +# generated while relay was offline and those in the next 100 seconds while script is active. +bridge-hub-westend-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds + diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..7d5b8d9273664b0861e8ffe1c528e9e1718c4df4 --- /dev/null +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/run.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -e + +source "${BASH_SOURCE%/*}/../../framework/utils/common.sh" +source "${BASH_SOURCE%/*}/../../framework/utils/zombienet.sh" + +export ENV_PATH=`realpath ${BASH_SOURCE%/*}/../../environments/rococo-westend` + +$ENV_PATH/spawn.sh & +env_pid=$! + +ensure_process_file $env_pid $TEST_DIR/rococo.env 600 +rococo_dir=`cat $TEST_DIR/rococo.env` +echo + +ensure_process_file $env_pid $TEST_DIR/westend.env 300 +westend_dir=`cat $TEST_DIR/westend.env` +echo + +# Sleep for some time before starting the relayer. We want to sleep for at least 1 session, +# which is expected to be 60 seconds for the test environment. +echo -e "Sleeping 90s before starting relayer ...\n" +sleep 90 +${BASH_SOURCE%/*}/../../environments/rococo-westend/start_relayer.sh $rococo_dir $westend_dir relayer_pid + +# Sometimes the relayer syncs multiple parachain heads in the begining leading to test failures. +# See issue: https://github.com/paritytech/parity-bridges-common/issues/2838. +# TODO: Remove this sleep after the issue is fixed. +echo -e "Sleeping 180s before runing the tests ...\n" +sleep 180 + +run_zndsl ${BASH_SOURCE%/*}/rococo-to-westend.zndsl $westend_dir +run_zndsl ${BASH_SOURCE%/*}/westend-to-rococo.zndsl $rococo_dir + diff --git a/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..b4b3e43679162feb8c3c5253f3f963d950f31d55 --- /dev/null +++ b/bridges/testing/tests/0002-mandatory-headers-synced-while-idle/westend-to-rococo.zndsl @@ -0,0 +1,7 @@ +Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH. +Network: {{ENV_PATH}}/bridge_hub_rococo_local_network.toml +Creds: config + +# ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were +# generated while relay was offline and those in the next 100 seconds while script is active. +bridge-hub-rococo-collator1: js-script {{FRAMEWORK_PATH}}/js-helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds diff --git a/bridges/zombienet/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl similarity index 77% rename from bridges/zombienet/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl rename to bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl index a4960344f0a03265d2accfa52cd9a4ab1d7117d6..07b91481dc7cf995b913a9bf84edd3728982eaae 100644 --- a/bridges/zombienet/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl +++ b/bridges/testing/tests/0003-required-headers-synced-while-active-rococo-to-westend.zndsl @@ -1,5 +1,5 @@ Description: While relayer is active, we only sync mandatory and required Rococo (and Rococo BH) headers to Westend BH. -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +Network: ../environments/rococo-westend/bridge_hub_westend_local_network.toml Creds: config # step 1: initialize Westend AH @@ -9,7 +9,7 @@ asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hu bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds # step 3: ensure that initialization has completed -asset-hub-westend-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds +asset-hub-westend-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds # step 4: send message from Westend to Rococo asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds @@ -20,7 +20,7 @@ asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-trans # (it is started by sibling 0003-required-headers-synced-while-active-westend-to-rococo.zndsl) # step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations -bridge-hub-westend-collator1: js-script ../helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds +bridge-hub-westend-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,rococo-at-westend" within 600 seconds # wait until other network test has completed OR exit with an error too asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/zombienet/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl similarity index 77% rename from bridges/zombienet/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl rename to bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl index 33c3ceebcf844cc6029d41deb289b1a1d8103132..a6b11fc24052aadf562bc34704aeda9ee115eccf 100644 --- a/bridges/zombienet/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl +++ b/bridges/testing/tests/0003-required-headers-synced-while-active-westend-to-rococo.zndsl @@ -1,5 +1,5 @@ Description: While relayer is active, we only sync mandatory and required Westend (and Westend BH) headers to Rococo BH. -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +Network: ../environments/rococo-westend/bridge_hub_rococo_local_network.toml Creds: config # step 1: initialize Rococo AH @@ -9,7 +9,7 @@ asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds # step 3: ensure that initialization has completed -asset-hub-rococo-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds +asset-hub-rococo-collator1: js-script ../js-helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds # step 4: send message from Rococo to Westend asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds @@ -20,7 +20,7 @@ asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transf bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds # step 6: ensure that relayer won't sync any extra headers while delivering messages and confirmations -bridge-hub-rococo-collator1: js-script ../helpers/only-required-headers-synced-when-active.js with "500,westend-at-rococo" within 600 seconds +bridge-hub-rococo-collator1: js-script ../js-helpers/only-required-headers-synced-when-active.js with "500,westend-at-rococo" within 600 seconds # wait until other network test has completed OR exit with an error too asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/zombienet/helpers/chains/rococo-at-westend.js b/bridges/zombienet/helpers/chains/rococo-at-westend.js deleted file mode 100644 index eb9510e46f0b7ba94e55968816accac185373c7c..0000000000000000000000000000000000000000 --- a/bridges/zombienet/helpers/chains/rococo-at-westend.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - grandpaPalletName: "bridgeRococoGrandpa", - parachainsPalletName: "bridgeRococoParachains", - messagesPalletName: "bridgeRococoMessages", - bridgedBridgeHubParaId: 1013, -} diff --git a/bridges/zombienet/helpers/chains/westend-at-rococo.js b/bridges/zombienet/helpers/chains/westend-at-rococo.js deleted file mode 100644 index 771a0778cb098c4e1e3a5c124c81cc38e2aac695..0000000000000000000000000000000000000000 --- a/bridges/zombienet/helpers/chains/westend-at-rococo.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - grandpaPalletName: "bridgeWestendGrandpa", - parachainsPalletName: "bridgeWestendParachains", - messagesPalletName: "bridgeWestendMessages", - bridgedBridgeHubParaId: 1002, -} diff --git a/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl b/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl deleted file mode 100644 index 82d1eee2f45cc12b60a85b829d4a4c17588fa9e7..0000000000000000000000000000000000000000 --- a/bridges/zombienet/tests/0001-asset-transfer-works-rococo-to-westend.zndsl +++ /dev/null @@ -1,39 +0,0 @@ -Description: User is able to transfer ROC from Rococo Asset Hub to Westend Asset Hub and back -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml -Creds: config - -# step 0: start relayer -# (started by sibling 0001-asset-transfer-works-westend-to-rococo.zndsl test) - -# step 1: initialize Westend AH -asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-westend-local" within 60 seconds - -# step 2: initialize Westend bridge hub -bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds - -# step 3: ensure that initialization has completed -asset-hub-westend-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1002" within 600 seconds - -# step 4: relay is already started - let's wait until with-Rococo GRANPDA pallet is initialized at Westend -bridge-hub-westend-collator1: js-script ../helpers/best-finalized-header-at-bridged-chain.js with "Rococo,0" within 400 seconds - -# step 5: send WND to //Alice on Rococo AH -# (that's a required part of a sibling 0001-asset-transfer-works-westend-to-rococo.zndsl test) -asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-westend-local" within 60 seconds - -# step 6: elsewhere Rococo has sent ROC to //Alice - let's wait for it -asset-hub-westend-collator1: js-script ../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Rococo" within 600 seconds - -# step 7: check that the relayer //Charlie is rewarded by both our AH and target AH -bridge-hub-westend-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726f,BridgedChain,0" within 300 seconds -bridge-hub-westend-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x6268726F,ThisChain,0" within 300 seconds - -# step 8: send wROC back to Alice at Rococo AH -asset-hub-westend-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-westend-local" within 60 seconds - -# step 9: elsewhere Rococo has sent wWND to //Alice - let's wait for it -# (we wait until //Alice account increases here - there are no other transactionc that may increase it) -asset-hub-westend-collator1: js-script ../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl b/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl deleted file mode 100644 index acfe0df03d26779abf0dd3c2aa3dfc8f37c0e3aa..0000000000000000000000000000000000000000 --- a/bridges/zombienet/tests/0001-asset-transfer-works-westend-to-rococo.zndsl +++ /dev/null @@ -1,39 +0,0 @@ -Description: User is able to transfer WND from Westend Asset Hub to Rococo Asset Hub and back -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml -Creds: config - -# step 0: start relayer -bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds - -# step 1: initialize Rococo AH -asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-asset-hub-rococo-local" within 60 seconds - -# step 2: initialize Rococo bridge hub -bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds - -# step 3: ensure that initialization has completed -asset-hub-rococo-collator1: js-script ../helpers/wait-hrmp-channel-opened.js with "1013" within 600 seconds - -# step 4: relay is already started - let's wait until with-Westend GRANPDA pallet is initialized at Rococo -bridge-hub-rococo-collator1: js-script ../helpers/best-finalized-header-at-bridged-chain.js with "Westend,0" within 400 seconds - -# step 5: send ROC to //Alice on Westend AH -# (that's a required part of a sibling 0001-asset-transfer-works-rococo-to-westend.zndsl test) -asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "reserve-transfer-assets-from-asset-hub-rococo-local" within 60 seconds - -# step 6: elsewhere Westend has sent WND to //Alice - let's wait for it -asset-hub-rococo-collator1: js-script ../helpers/wrapped-assets-balance.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY,0,Westend" within 600 seconds - -# step 7: check that the relayer //Charlie is rewarded by both our AH and target AH -bridge-hub-rococo-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,BridgedChain,0" within 300 seconds -bridge-hub-rococo-collator1: js-script ../helpers/relayer-rewards.js with "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y,0x00000002,0x62687764,ThisChain,0" within 300 seconds - -# step 8: send wWND back to Alice at Westend AH -asset-hub-rococo-collator1: run ../scripts/invoke-script.sh with "withdraw-reserve-assets-from-asset-hub-rococo-local" within 60 seconds - -# step 9: elsewhere Westend has sent wROC to //Alice - let's wait for it -# (we wait until //Alice account increases here - there are no other transactionc that may increase it) -asset-hub-rococo-collator1: js-script ../helpers/native-assets-balance-increased.js with "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl deleted file mode 100644 index eb6a75c373c7add04f895c01e332d40195150370..0000000000000000000000000000000000000000 --- a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-rococo-to-westend.zndsl +++ /dev/null @@ -1,26 +0,0 @@ -Description: While relayer is idle, we only sync mandatory Rococo (and a single Rococo BH) headers to Westend BH. -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml -Creds: config - -# step 1: initialize Westend bridge hub -bridge-hub-westend-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-westend-local" within 60 seconds - -# step 2: sleep some time before starting relayer. We want to sleep for at least 1 session, which is expected to -# be 60 seconds for test environment. -sleep 120 seconds - -# step 3: start relayer -# (it is started by the sibling 0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl test file) - -# it also takes some time for relayer to initialize bridge, so let's sleep for 5 minutes to be sure that parachain -# header has been synced - -# step 4: ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were -# born while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-westend-collator1: js-script ../helpers/only-mandatory-headers-synced-when-idle.js with "300,rococo-at-westend" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-westend-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl b/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl deleted file mode 100644 index 728d54d586a9b46625e3db70251b68c6501db922..0000000000000000000000000000000000000000 --- a/bridges/zombienet/tests/0002-mandatory-headers-synced-while-idle-westend-to-rococo.zndsl +++ /dev/null @@ -1,26 +0,0 @@ -Description: While relayer is idle, we only sync mandatory Westend (and a single Westend BH) headers to Rococo BH. -Network: ../../../cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml -Creds: config - -# step 1: initialize Rococo bridge hub -bridge-hub-rococo-collator1: run ../scripts/invoke-script.sh with "init-bridge-hub-rococo-local" within 60 seconds - -# step 2: sleep some time before starting relayer. We want to sleep for at least 1 session, which is expected to -# be 60 seconds for test environment. -sleep 120 seconds - -# step 3: start relayer -bridge-hub-rococo-collator1: run ../scripts/start-relayer.sh within 60 seconds - -# it also takes some time for relayer to initialize bridge, so let's sleep for 5 minutes to be sure that parachain -# header has been synced - -# step 4: ensure that relayer is only syncing mandatory headers while idle. This includes both headers that were -# born while relay was offline and those in the next 100 seconds while script is active. -bridge-hub-rococo-collator1: js-script ../helpers/only-mandatory-headers-synced-when-idle.js with "300,westend-at-rococo" within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds - -# wait until other network test has completed OR exit with an error too -asset-hub-rococo-collator1: run ../scripts/sync-exit.sh within 600 seconds diff --git a/cumulus/client/cli/Cargo.toml b/cumulus/client/cli/Cargo.toml index 0495eab9bd5bc2711e256a7cbb0c06ae13b1f2bc..eaf0d5d5d7f78e578644bf35f83d9543ad9af4bd 100644 --- a/cumulus/client/cli/Cargo.toml +++ b/cumulus/client/cli/Cargo.toml @@ -10,7 +10,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } url = "2.4.0" diff --git a/cumulus/client/cli/src/lib.rs b/cumulus/client/cli/src/lib.rs index 1807b8a1718e8b5c800b3bf27b58e0f39cd2948a..a7b2eb19de88a5c585ec3f6dfe5ad46ef0399b88 100644 --- a/cumulus/client/cli/src/lib.rs +++ b/cumulus/client/cli/src/lib.rs @@ -30,7 +30,7 @@ use codec::Encode; use sc_chain_spec::ChainSpec; use sc_client_api::HeaderBackend; use sc_service::{ - config::{PrometheusConfig, TelemetryEndpoints}, + config::{PrometheusConfig, RpcBatchRequestConfig, TelemetryEndpoints}, BasePath, TransactionPoolOptions, }; use sp_core::hexdisplay::HexDisplay; @@ -443,6 +443,14 @@ impl sc_cli::CliConfiguration for NormalizedRunCmd { Ok(self.base.rpc_max_subscriptions_per_connection) } + fn rpc_buffer_capacity_per_connection(&self) -> sc_cli::Result { + Ok(self.base.rpc_message_buffer_capacity_per_connection) + } + + fn rpc_batch_config(&self) -> sc_cli::Result { + self.base.rpc_batch_config() + } + fn transaction_pool(&self, is_dev: bool) -> sc_cli::Result { self.base.transaction_pool(is_dev) } diff --git a/cumulus/client/consensus/aura/src/collator.rs b/cumulus/client/consensus/aura/src/collator.rs index db0799235bca27aaa4456da6c8649b0b76fef030..5b7669c88f473b8765b6b343d1797aa707ed5916 100644 --- a/cumulus/client/consensus/aura/src/collator.rs +++ b/cumulus/client/consensus/aura/src/collator.rs @@ -258,6 +258,7 @@ where pub struct SlotClaim { author_pub: Pub, pre_digest: DigestItem, + slot: Slot, timestamp: Timestamp, } @@ -272,7 +273,7 @@ impl SlotClaim { P::Public: Codec, P::Signature: Codec, { - SlotClaim { author_pub, timestamp, pre_digest: aura_internal::pre_digest::

(slot) } + SlotClaim { author_pub, timestamp, pre_digest: aura_internal::pre_digest::

(slot), slot } } /// Get the author's public key. @@ -285,6 +286,11 @@ impl SlotClaim { &self.pre_digest } + /// Get the slot assigned to this claim. + pub fn slot(&self) -> Slot { + self.slot + } + /// Get the timestamp corresponding to the relay-chain slot this claim was /// generated against. pub fn timestamp(&self) -> Timestamp { diff --git a/cumulus/client/consensus/aura/src/collators/basic.rs b/cumulus/client/consensus/aura/src/collators/basic.rs index 78f6b726aff0cb63cd08259c327bfbda71c05b8b..52b83254951f0e0ba0fd9ad5420d7faca2402066 100644 --- a/cumulus/client/consensus/aura/src/collators/basic.rs +++ b/cumulus/client/consensus/aura/src/collators/basic.rs @@ -33,12 +33,12 @@ use cumulus_relay_chain_interface::RelayChainInterface; use polkadot_node_primitives::CollationResult; use polkadot_overseer::Handle as OverseerHandle; -use polkadot_primitives::{CollatorPair, Id as ParaId}; +use polkadot_primitives::{CollatorPair, Id as ParaId, ValidationCode}; use futures::{channel::mpsc::Receiver, prelude::*}; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf}; use sc_consensus::BlockImport; -use sp_api::ProvideRuntimeApi; +use sp_api::{CallApiAt, ProvideRuntimeApi}; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus::SyncOracle; @@ -47,6 +47,7 @@ use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; +use sp_state_machine::Backend as _; use std::{convert::TryFrom, sync::Arc, time::Duration}; use crate::collator as collator_util; @@ -100,6 +101,7 @@ where + AuxStore + HeaderBackend + BlockBackend + + CallApiAt + Send + Sync + 'static, @@ -141,6 +143,8 @@ where collator_util::Collator::::new(params) }; + let mut last_processed_slot = 0; + while let Some(request) = collation_requests.next().await { macro_rules! reject_with_error { ($err:expr) => {{ @@ -170,6 +174,22 @@ where continue } + let Ok(Some(code)) = + params.para_client.state_at(parent_hash).map_err(drop).and_then(|s| { + s.storage(&sp_core::storage::well_known_keys::CODE).map_err(drop) + }) + else { + continue; + }; + + super::check_validation_code_or_log( + &ValidationCode::from(code).hash(), + params.para_id, + ¶ms.relay_client, + *request.relay_parent(), + ) + .await; + let relay_parent_header = match params.relay_client.header(RBlockId::hash(*request.relay_parent())).await { Err(e) => reject_with_error!(e), @@ -192,6 +212,18 @@ where Err(e) => reject_with_error!(e), }; + // With async backing this function will be called every relay chain block. + // + // Most parachains currently run with 12 seconds slots and thus, they would try to + // produce multiple blocks per slot which very likely would fail on chain. Thus, we have + // this "hack" to only produce on block per slot. + // + // With https://github.com/paritytech/polkadot-sdk/issues/3168 this implementation will be + // obsolete and also the underlying issue will be fixed. + if last_processed_slot >= *claim.slot() { + continue + } + let (parachain_inherent_data, other_inherent_data) = try_request!( collator .create_inherent_data( @@ -228,6 +260,8 @@ where request.complete(None); tracing::debug!(target: crate::LOG_TARGET, "No block proposal"); } + + last_processed_slot = *claim.slot(); } } } diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index e24b7f6f1c93b9bbe92cdf9ce5958194065862ae..161f10d55a193de35a2585e1a1f5725f30e19bf7 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -59,7 +59,7 @@ use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus::SyncOracle; -use sp_consensus_aura::{AuraApi, Slot, SlotDuration}; +use sp_consensus_aura::{AuraApi, Slot}; use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; @@ -95,8 +95,6 @@ pub struct Params { pub para_id: ParaId, /// A handle to the relay-chain client's "Overseer" or task orchestrator. pub overseer_handle: OverseerHandle, - /// The length of slots in this chain. - pub slot_duration: SlotDuration, /// The length of slots in the relay chain. pub relay_chain_slot_duration: Duration, /// The underlying block proposer this should call into. @@ -214,26 +212,6 @@ where }, }; - let (slot_now, timestamp) = match consensus_common::relay_slot_and_timestamp( - &relay_parent_header, - params.relay_chain_slot_duration, - ) { - None => continue, - Some((r_s, t)) => { - let our_slot = Slot::from_timestamp(t, params.slot_duration); - tracing::debug!( - target: crate::LOG_TARGET, - relay_slot = ?r_s, - para_slot = ?our_slot, - timestamp = ?t, - slot_duration = ?params.slot_duration, - relay_chain_slot_duration = ?params.relay_chain_slot_duration, - "Adjusted relay-chain slot to parachain slot" - ); - (our_slot, t) - }, - }; - let parent_search_params = ParentSearchParams { relay_parent, para_id: params.para_id, @@ -272,14 +250,39 @@ where let para_client = &*params.para_client; let keystore = ¶ms.keystore; let can_build_upon = |block_hash| { - can_build_upon::<_, _, P>( + let slot_duration = match sc_consensus_aura::standalone::slot_duration_at( + &*params.para_client, + block_hash, + ) { + Ok(sd) => sd, + Err(err) => { + tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to acquire parachain slot duration"); + return None + }, + }; + tracing::debug!(target: crate::LOG_TARGET, ?slot_duration, ?block_hash, "Parachain slot duration acquired"); + let (relay_slot, timestamp) = consensus_common::relay_slot_and_timestamp( + &relay_parent_header, + params.relay_chain_slot_duration, + )?; + let slot_now = Slot::from_timestamp(timestamp, slot_duration); + tracing::debug!( + target: crate::LOG_TARGET, + ?relay_slot, + para_slot = ?slot_now, + ?timestamp, + ?slot_duration, + relay_chain_slot_duration = ?params.relay_chain_slot_duration, + "Adjusted relay-chain slot to parachain slot" + ); + Some(can_build_upon::<_, _, P>( slot_now, timestamp, block_hash, included_block, para_client, &keystore, - ) + )) }; // Sort by depth, ascending, to choose the longest chain. @@ -287,10 +290,7 @@ where // If the longest chain has space, build upon that. Otherwise, don't // build at all. potential_parents.sort_by_key(|a| a.depth); - let initial_parent = match potential_parents.pop() { - None => continue, - Some(p) => p, - }; + let Some(initial_parent) = potential_parents.pop() else { continue }; // Build in a loop until not allowed. Note that the authorities can change // at any block, so we need to re-claim our slot every time. @@ -298,12 +298,19 @@ where let mut parent_header = initial_parent.header; let overseer_handle = &mut params.overseer_handle; + // We mainly call this to inform users at genesis if there is a mismatch with the + // on-chain data. + collator.collator_service().check_block_status(parent_hash, &parent_header); + // This needs to change to support elastic scaling, but for continuously // scheduled chains this ensures that the backlog will grow steadily. for n_built in 0..2 { - let slot_claim = match can_build_upon(parent_hash).await { + let slot_claim = match can_build_upon(parent_hash) { + Some(fut) => match fut.await { + None => break, + Some(c) => c, + }, None => break, - Some(c) => c, }; tracing::debug!( @@ -347,6 +354,14 @@ where Some(v) => v, }; + super::check_validation_code_or_log( + &validation_code_hash, + params.para_id, + ¶ms.relay_client, + relay_parent, + ) + .await; + match collator .collate( &parent_header, diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 4c7b759daf736f69de48b586b082a7d01534d7e3..6e0067d0cedb602face8943737f99f3cb1a201a3 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -20,5 +20,60 @@ //! included parachain block, as well as the [`lookahead`] collator, which prospectively //! builds on parachain blocks which have not yet been included in the relay chain. +use cumulus_relay_chain_interface::RelayChainInterface; +use polkadot_primitives::{ + Hash as RHash, Id as ParaId, OccupiedCoreAssumption, ValidationCodeHash, +}; + pub mod basic; pub mod lookahead; + +/// Check the `local_validation_code_hash` against the validation code hash in the relay chain +/// state. +/// +/// If the code hashes do not match, it prints a warning. +async fn check_validation_code_or_log( + local_validation_code_hash: &ValidationCodeHash, + para_id: ParaId, + relay_client: &impl RelayChainInterface, + relay_parent: RHash, +) { + let state_validation_code_hash = match relay_client + .validation_code_hash(relay_parent, para_id, OccupiedCoreAssumption::Included) + .await + { + Ok(hash) => hash, + Err(error) => { + tracing::debug!( + target: super::LOG_TARGET, + %error, + ?relay_parent, + %para_id, + "Failed to fetch validation code hash", + ); + return + }, + }; + + match state_validation_code_hash { + Some(state) => + if state != *local_validation_code_hash { + tracing::warn!( + target: super::LOG_TARGET, + %para_id, + ?relay_parent, + ?local_validation_code_hash, + relay_validation_code_hash = ?state, + "Parachain code doesn't match validation code stored in the relay chain state", + ); + }, + None => { + tracing::warn!( + target: super::LOG_TARGET, + %para_id, + ?relay_parent, + "Could not find validation code for parachain in the relay chain state.", + ); + }, + } +} diff --git a/cumulus/client/consensus/aura/src/lib.rs b/cumulus/client/consensus/aura/src/lib.rs index 6ededa7a92c11cb8c313f7da01017eeef256fb06..8e4bc658e44bfdc173f3fdb66518da883edcb05b 100644 --- a/cumulus/client/consensus/aura/src/lib.rs +++ b/cumulus/client/consensus/aura/src/lib.rs @@ -42,7 +42,14 @@ use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member, NumberFor}; -use std::{convert::TryFrom, marker::PhantomData, sync::Arc}; +use std::{ + convert::TryFrom, + marker::PhantomData, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, + }, +}; mod import_queue; @@ -61,6 +68,7 @@ pub struct AuraConsensus { create_inherent_data_providers: Arc, aura_worker: Arc>, slot_duration: SlotDuration, + last_slot_processed: Arc, _phantom: PhantomData, } @@ -70,6 +78,7 @@ impl Clone for AuraConsensus { create_inherent_data_providers: self.create_inherent_data_providers.clone(), aura_worker: self.aura_worker.clone(), slot_duration: self.slot_duration, + last_slot_processed: self.last_slot_processed.clone(), _phantom: PhantomData, } } @@ -156,6 +165,7 @@ where Box::new(AuraConsensus { create_inherent_data_providers: Arc::new(create_inherent_data_providers), aura_worker: Arc::new(Mutex::new(worker)), + last_slot_processed: Default::default(), slot_duration, _phantom: PhantomData, }) @@ -221,6 +231,18 @@ where Some((validation_data.max_pov_size / 2) as usize), ); + // With async backing this function will be called every relay chain block. + // + // Most parachains currently run with 12 seconds slots and thus, they would try to produce + // multiple blocks per slot which very likely would fail on chain. Thus, we have this "hack" + // to only produce on block per slot. + // + // With https://github.com/paritytech/polkadot-sdk/issues/3168 this implementation will be + // obsolete and also the underlying issue will be fixed. + if self.last_slot_processed.fetch_max(*info.slot, Ordering::Relaxed) >= *info.slot { + return None + } + let res = self.aura_worker.lock().await.on_slot(info).await?; Some(ParachainCandidate { block: res.block, proof: res.storage_proof }) diff --git a/cumulus/client/consensus/common/Cargo.toml b/cumulus/client/consensus/common/Cargo.toml index 7fee51310d01e2e6696c4cbb20ba10dade106dbc..5a014b10e35f39b0a5e00ca01da7cfd3ecc50a5f 100644 --- a/cumulus/client/consensus/common/Cargo.toml +++ b/cumulus/client/consensus/common/Cargo.toml @@ -14,7 +14,7 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } dyn-clone = "1.0.16" futures = "0.3.28" -log = "0.4.20" +log = { workspace = true, default-features = true } tracing = "0.1.37" # Substrate diff --git a/cumulus/client/consensus/common/src/tests.rs b/cumulus/client/consensus/common/src/tests.rs index 597d1ab2acc2cff42d3230898c1129a7ba63b6f3..bfb95ae388ae3cd31f5035a9c6195631adbb8809 100644 --- a/cumulus/client/consensus/common/src/tests.rs +++ b/cumulus/client/consensus/common/src/tests.rs @@ -136,6 +136,15 @@ impl RelayChainInterface for Relaychain { Ok(Some(PersistedValidationData { parent_head, ..Default::default() })) } + async fn validation_code_hash( + &self, + _: PHash, + _: ParaId, + _: OccupiedCoreAssumption, + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } + async fn candidate_pending_availability( &self, _: PHash, diff --git a/cumulus/client/consensus/proposer/Cargo.toml b/cumulus/client/consensus/proposer/Cargo.toml index 8a559c603f33c138912cfc096e2d90cc879b597e..b37232bb4485d6f5ece63a3c940bd065c1d3f083 100644 --- a/cumulus/client/consensus/proposer/Cargo.toml +++ b/cumulus/client/consensus/proposer/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] anyhow = "1.0" async-trait = "0.1.74" -thiserror = "1.0.48" +thiserror = { workspace = true } # Substrate sp-consensus = { path = "../../../../substrate/primitives/consensus/common" } diff --git a/cumulus/client/network/src/tests.rs b/cumulus/client/network/src/tests.rs index e03f470753bb6c32c4410a7c694dea8312c74c31..d986635f961c914c9ec6fa970b20170f9f8b9cbc 100644 --- a/cumulus/client/network/src/tests.rs +++ b/cumulus/client/network/src/tests.rs @@ -117,6 +117,15 @@ impl RelayChainInterface for DummyRelayChainInterface { })) } + async fn validation_code_hash( + &self, + _: PHash, + _: ParaId, + _: OccupiedCoreAssumption, + ) -> RelayChainResult> { + unimplemented!("Not needed for test") + } + async fn candidate_pending_availability( &self, _: PHash, diff --git a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs index 866214fe2c526d77153afbea74b130aa1fda8636..6ea02b2e7c1f6d9b5313459890dd2147015359e5 100644 --- a/cumulus/client/relay-chain-inprocess-interface/src/lib.rs +++ b/cumulus/client/relay-chain-inprocess-interface/src/lib.rs @@ -21,7 +21,7 @@ use cumulus_primitives_core::{ relay_chain::{ runtime_api::ParachainHost, Block as PBlock, BlockId, CommittedCandidateReceipt, Hash as PHash, Header as PHeader, InboundHrmpMessage, OccupiedCoreAssumption, SessionIndex, - ValidatorId, + ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -115,6 +115,19 @@ impl RelayChainInterface for RelayChainInProcessInterface { )?) } + async fn validation_code_hash( + &self, + hash: PHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult> { + Ok(self.full_client.runtime_api().validation_code_hash( + hash, + para_id, + occupied_core_assumption, + )?) + } + async fn candidate_pending_availability( &self, hash: PHash, diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml index 004d30f7f94f70b7ed2e25fb9e1eebd9acc7c586..6e652b892104e929e5e0a5bb7ce8cf33d364e8e6 100644 --- a/cumulus/client/relay-chain-interface/Cargo.toml +++ b/cumulus/client/relay-chain-interface/Cargo.toml @@ -21,6 +21,6 @@ sc-client-api = { path = "../../../substrate/client/api" } futures = "0.3.28" async-trait = "0.1.74" -thiserror = "1.0.48" -jsonrpsee-core = "0.20.3" +thiserror = { workspace = true } +jsonrpsee-core = "0.22" parity-scale-codec = "3.6.4" diff --git a/cumulus/client/relay-chain-interface/src/lib.rs b/cumulus/client/relay-chain-interface/src/lib.rs index 3dda61635804d47440689872271751e0bba27d87..bb93e6a168c849fa9d41586ad8b9ec0013e6c01f 100644 --- a/cumulus/client/relay-chain-interface/src/lib.rs +++ b/cumulus/client/relay-chain-interface/src/lib.rs @@ -22,7 +22,7 @@ use sc_client_api::StorageProof; use futures::Stream; use async_trait::async_trait; -use jsonrpsee_core::Error as JsonRpcError; +use jsonrpsee_core::ClientError as JsonRpcError; use parity_scale_codec::Error as CodecError; use sp_api::ApiError; @@ -30,7 +30,7 @@ use cumulus_primitives_core::relay_chain::BlockId; pub use cumulus_primitives_core::{ relay_chain::{ CommittedCandidateReceipt, Hash as PHash, Header as PHeader, InboundHrmpMessage, - OccupiedCoreAssumption, SessionIndex, ValidatorId, + OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -194,6 +194,15 @@ pub trait RelayChainInterface: Send + Sync { relay_parent: PHash, relevant_keys: &Vec>, ) -> RelayChainResult; + + /// Returns the validation code hash for the given `para_id` using the given + /// `occupied_core_assumption`. + async fn validation_code_hash( + &self, + relay_parent: PHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult>; } #[async_trait] @@ -301,4 +310,15 @@ where async fn header(&self, block_id: BlockId) -> RelayChainResult> { (**self).header(block_id).await } + + async fn validation_code_hash( + &self, + relay_parent: PHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult> { + (**self) + .validation_code_hash(relay_parent, para_id, occupied_core_assumption) + .await + } } diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index fef6f82537d7e5760dc71bc0b492276926f3bc2e..801712b1ad150a8a63e85c40ec0854a62b5969bc 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -33,16 +33,16 @@ tokio-util = { version = "0.7.8", features = ["compat"] } futures = "0.3.28" futures-timer = "3.0.2" parity-scale-codec = "3.6.4" -jsonrpsee = { version = "0.20.3", features = ["ws-client"] } +jsonrpsee = { version = "0.22", features = ["ws-client"] } tracing = "0.1.37" async-trait = "0.1.74" url = "2.4.0" -serde_json = "1.0.111" -serde = "1.0.195" +serde_json = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } schnellru = "0.2.1" smoldot = { version = "0.11.0", default_features = false, features = ["std"] } smoldot-light = { version = "0.9.0", default_features = false, features = ["std"] } either = "1.8.1" -thiserror = "1.0.48" +thiserror = { workspace = true } rand = "0.8.5" pin-project = "1.1.3" diff --git a/cumulus/client/relay-chain-rpc-interface/src/lib.rs b/cumulus/client/relay-chain-rpc-interface/src/lib.rs index 96f8fc8b5563335eb7796bc8fd105fced15bc5e1..3a4c186e301eab295aa3befbd3c5549636fdb2c0 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/lib.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/lib.rs @@ -19,7 +19,7 @@ use core::time::Duration; use cumulus_primitives_core::{ relay_chain::{ CommittedCandidateReceipt, Hash as RelayHash, Header as RelayHeader, InboundHrmpMessage, - OccupiedCoreAssumption, SessionIndex, ValidatorId, + OccupiedCoreAssumption, SessionIndex, ValidationCodeHash, ValidatorId, }, InboundDownwardMessage, ParaId, PersistedValidationData, }; @@ -110,6 +110,17 @@ impl RelayChainInterface for RelayChainRpcInterface { .await } + async fn validation_code_hash( + &self, + hash: RelayHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> RelayChainResult> { + self.rpc_client + .validation_code_hash(hash, para_id, occupied_core_assumption) + .await + } + async fn candidate_pending_availability( &self, hash: RelayHash, diff --git a/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs b/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs index 6fd057e170b715a1586dbd2dbf3f608268c539bd..9a49b60281b3c51fa1426903a0e73157a6f04e0e 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/light_client_worker.rs @@ -19,12 +19,9 @@ //! we treat the light-client as a normal JsonRPC target. use futures::{channel::mpsc::Sender, prelude::*, stream::FuturesUnordered}; -use jsonrpsee::core::{ - client::{ - Client as JsonRpseeClient, ClientBuilder, ClientT, ReceivedMessage, TransportReceiverT, - TransportSenderT, - }, - Error, +use jsonrpsee::core::client::{ + Client as JsonRpseeClient, ClientBuilder, ClientT, Error, ReceivedMessage, TransportReceiverT, + TransportSenderT, }; use smoldot_light::{ChainId, Client as SmoldotClient, JsonRpcResponses}; use std::{num::NonZeroU32, sync::Arc}; diff --git a/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs b/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs index 322bcc93dae6d8158a0e6cfb99ded9a29bee79c5..b716feef1c998d66eba3c5ea28ee1dd98c4959e3 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/reconnecting_ws_client.rs @@ -27,7 +27,7 @@ use jsonrpsee::{ core::{ client::{Client as JsonRpcClient, ClientT, Subscription}, params::ArrayParams, - Error as JsonRpseeError, JsonValue, + ClientError as JsonRpseeError, JsonValue, }, ws_client::WsClientBuilder, }; diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index c64fff77a29fd016d7e1723ab461dc8082770682..6578210a259c956b1c817f579f66e84258a8ab3e 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -19,7 +19,7 @@ use futures::channel::{ oneshot::Sender as OneshotSender, }; use jsonrpsee::{ - core::{params::ArrayParams, Error as JsonRpseeError}, + core::{params::ArrayParams, ClientError as JsonRpseeError}, rpc_params, }; use serde::de::DeserializeOwned; @@ -647,6 +647,20 @@ impl RelayChainRpcClient { .await } + pub async fn validation_code_hash( + &self, + at: RelayHash, + para_id: ParaId, + occupied_core_assumption: OccupiedCoreAssumption, + ) -> Result, RelayChainError> { + self.call_remote_runtime_function( + "ParachainHost_validation_code_hash", + at, + Some((para_id, occupied_core_assumption)), + ) + .await + } + fn send_register_message_to_worker( &self, message: RpcDispatcherMessage, diff --git a/cumulus/pallets/aura-ext/src/lib.rs b/cumulus/pallets/aura-ext/src/lib.rs index 34a41557152d8df38e0abc2d612151b2f74dc779..31b571816a0c15837c6175ab233204565cc7eb36 100644 --- a/cumulus/pallets/aura-ext/src/lib.rs +++ b/cumulus/pallets/aura-ext/src/lib.rs @@ -117,12 +117,6 @@ pub mod pallet { impl BuildGenesisConfig for GenesisConfig { fn build(&self) { let authorities = Aura::::authorities(); - - assert!( - !authorities.is_empty(), - "AuRa authorities empty, maybe wrong order in `construct_runtime!`?", - ); - Authorities::::put(authorities); } } diff --git a/cumulus/pallets/collator-selection/Cargo.toml b/cumulus/pallets/collator-selection/Cargo.toml index 4216776fe8ac017958462aec1da5af58edc67f39..20f048b97d558962ea270ef51399f6d2905ab1a0 100644 --- a/cumulus/pallets/collator-selection/Cargo.toml +++ b/cumulus/pallets/collator-selection/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.20", default-features = false } +log = { workspace = true } codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.0.0" } rand = { version = "0.8.5", features = ["std_rng"], default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/cumulus/pallets/collator-selection/src/benchmarking.rs b/cumulus/pallets/collator-selection/src/benchmarking.rs index fa95303495dd1039f93f79baf93dc5301a31840d..2c40f4dd0eac4a8a2ad3aed7f162a4379ccdbc93 100644 --- a/cumulus/pallets/collator-selection/src/benchmarking.rs +++ b/cumulus/pallets/collator-selection/src/benchmarking.rs @@ -394,7 +394,7 @@ mod benchmarks { register_validators::(c); register_candidates::(c); - let new_block: BlockNumberFor = 1800u32.into(); + let new_block: BlockNumberFor = T::KickThreshold::get(); let zero_block: BlockNumberFor = 0u32.into(); let candidates: Vec = >::get() .iter() diff --git a/cumulus/pallets/dmp-queue/Cargo.toml b/cumulus/pallets/dmp-queue/Cargo.toml index 301a77003cb6a08c2cac2c1feec48fe333959bb1..83ed994d04167607e3df54587c49e6b88576d4ec 100644 --- a/cumulus/pallets/dmp-queue/Cargo.toml +++ b/cumulus/pallets/dmp-queue/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"] } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 848efd3eab67c1b02473e6d0a62e75efcac16f35..7e0442f0b5856fa5153e29ff497cfee876c067ec 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -14,7 +14,7 @@ bytes = { version = "1.4.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } environmental = { version = "1.1.4", default-features = false } impl-trait-for-tuples = "0.2.1" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } trie-db = { version = "0.28.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } @@ -36,6 +36,7 @@ sp-version = { path = "../../../substrate/primitives/version", default-features # Polkadot polkadot-parachain-primitives = { path = "../../../polkadot/parachain", default-features = false, features = ["wasm-api"] } polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", default-features = false } +polkadot-runtime-common = { path = "../../../polkadot/runtime/common", default-features = false, optional = true } xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } # Cumulus @@ -79,6 +80,7 @@ std = [ "log/std", "pallet-message-queue/std", "polkadot-parachain-primitives/std", + "polkadot-runtime-common/std", "polkadot-runtime-parachains/std", "scale-info/std", "sp-core/std", @@ -102,6 +104,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] @@ -110,6 +113,7 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-message-queue/try-runtime", + "polkadot-runtime-common?/try-runtime", "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", ] diff --git a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml index 5f41e6423ac33cef4db6689554e92d04ececdf92..0a90c30e0331261026125f429efe70eff07ac069 100644 --- a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml +++ b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml @@ -13,9 +13,9 @@ workspace = true proc-macro = true [dependencies] -syn = "2.0.48" +syn = { workspace = true } proc-macro2 = "1.0.64" -quote = "1.0.33" +quote = { workspace = true } proc-macro-crate = "3.0.0" [features] diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 5a0fa57fb171c60f13b87d70305a6d58dad475a1..d86a67e58f44713dfe7b1c9f5c0952c16d590986 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -1610,6 +1610,15 @@ impl UpwardMessageSender for Pallet { } } +#[cfg(feature = "runtime-benchmarks")] +impl polkadot_runtime_common::xcm_sender::EnsureForParachain for Pallet { + fn ensure(para_id: ParaId) { + if let ChannelStatus::Closed = Self::get_channel_status(para_id) { + Self::open_outbound_hrmp_channel_for_benchmarks_or_tests(para_id) + } + } +} + /// Something that can check the inherents of a block. #[cfg_attr( feature = "parameterized-consensus-hook", diff --git a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs index ce3b724420f1c9e50b65a81074e023c6b735227b..ecab7a9a09311ae0f952a7842e9f1826b00adc69 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs @@ -16,7 +16,7 @@ //! The actual implementation of the validate block functionality. -use super::{trie_cache, MemoryOptimizedValidationParams}; +use super::{trie_cache, trie_recorder, MemoryOptimizedValidationParams}; use cumulus_primitives_core::{ relay_chain::Hash as RHash, ParachainBlockData, PersistedValidationData, }; @@ -34,12 +34,14 @@ use sp_externalities::{set_and_run_with_externalities, Externalities}; use sp_io::KillStorageResult; use sp_runtime::traits::{Block as BlockT, Extrinsic, HashingFor, Header as HeaderT}; use sp_std::prelude::*; -use sp_trie::MemoryDB; +use sp_trie::{MemoryDB, ProofSizeProvider}; +use trie_recorder::SizeOnlyRecorderProvider; type TrieBackend = sp_state_machine::TrieBackend< MemoryDB>, HashingFor, trie_cache::CacheProvider>, + SizeOnlyRecorderProvider>, >; type Ext<'a, B> = sp_state_machine::Ext<'a, HashingFor, TrieBackend>; @@ -48,6 +50,9 @@ fn with_externalities R, R>(f: F) -> R { sp_externalities::with_externalities(f).expect("Environmental externalities not set.") } +// Recorder instance to be used during this validate_block call. +environmental::environmental!(recorder: trait ProofSizeProvider); + /// Validate the given parachain block. /// /// This function is doing roughly the following: @@ -120,6 +125,7 @@ where sp_std::mem::drop(storage_proof); + let mut recorder = SizeOnlyRecorderProvider::new(); let cache_provider = trie_cache::CacheProvider::new(); // We use the storage root of the `parent_head` to ensure that it is the correct root. // This is already being done above while creating the in-memory db, but let's be paranoid!! @@ -128,6 +134,7 @@ where *parent_header.state_root(), cache_provider, ) + .with_recorder(recorder.clone()) .build(); let _guard = ( @@ -167,9 +174,11 @@ where .replace_implementation(host_default_child_storage_next_key), sp_io::offchain_index::host_set.replace_implementation(host_offchain_index_set), sp_io::offchain_index::host_clear.replace_implementation(host_offchain_index_clear), + cumulus_primitives_proof_size_hostfunction::storage_proof_size::host_storage_proof_size + .replace_implementation(host_storage_proof_size), ); - run_with_externalities::(&backend, || { + run_with_externalities_and_recorder::(&backend, &mut recorder, || { let relay_chain_proof = crate::RelayChainStateProof::new( PSC::SelfParaId::get(), inherent_data.validation_data.relay_parent_storage_root, @@ -190,7 +199,7 @@ where } }); - run_with_externalities::(&backend, || { + run_with_externalities_and_recorder::(&backend, &mut recorder, || { let head_data = HeadData(block.header().encode()); E::execute_block(block); @@ -265,15 +274,17 @@ fn validate_validation_data( ); } -/// Run the given closure with the externalities set. -fn run_with_externalities R>( +/// Run the given closure with the externalities and recorder set. +fn run_with_externalities_and_recorder R>( backend: &TrieBackend, + recorder: &mut SizeOnlyRecorderProvider>, execute: F, ) -> R { let mut overlay = sp_state_machine::OverlayedChanges::default(); let mut ext = Ext::::new(&mut overlay, backend); + recorder.reset(); - set_and_run_with_externalities(&mut ext, || execute()) + recorder::using(recorder, || set_and_run_with_externalities(&mut ext, || execute())) } fn host_storage_read(key: &[u8], value_out: &mut [u8], value_offset: u32) -> Option { @@ -305,6 +316,10 @@ fn host_storage_clear(key: &[u8]) { with_externalities(|ext| ext.place_storage(key.to_vec(), None)) } +fn host_storage_proof_size() -> u64 { + recorder::with(|rec| rec.estimate_encoded_size()).expect("Recorder is always set; qed") as _ +} + fn host_storage_root(version: StateVersion) -> Vec { with_externalities(|ext| ext.storage_root(version)) } diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index f17ac6007a09bf02011473f95a537855eb43f3b0..a9fb65e11089fab87c543fbb6d7b54f6231a1dd7 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -59,7 +59,7 @@ fn call_validate_block( } fn create_test_client() -> (Client, Header) { - let client = TestClientBuilder::new().build(); + let client = TestClientBuilder::new().enable_import_proof_recording().build(); let genesis_header = client .header(client.chain_info().genesis_hash) diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs index e73aef70aa491fc68aad4f9479222d9a076e7edc..48310670c074d1be2ec86f582c0c84622abc086c 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs @@ -97,6 +97,7 @@ pub(crate) struct SizeOnlyRecorderProvider { } impl SizeOnlyRecorderProvider { + /// Create a new instance of [`SizeOnlyRecorderProvider`] pub fn new() -> Self { Self { seen_nodes: Default::default(), @@ -104,6 +105,13 @@ impl SizeOnlyRecorderProvider { recorded_keys: Default::default(), } } + + /// Reset the internal state. + pub fn reset(&self) { + self.seen_nodes.borrow_mut().clear(); + *self.encoded_size.borrow_mut() = 0; + self.recorded_keys.borrow_mut().clear(); + } } impl sp_trie::TrieRecorderProvider for SizeOnlyRecorderProvider { @@ -281,6 +289,9 @@ mod tests { reference_recorder.estimate_encoded_size(), recorder_for_test.estimate_encoded_size() ); + + recorder_for_test.reset(); + assert_eq!(recorder_for_test.estimate_encoded_size(), 0) } } } diff --git a/cumulus/pallets/xcmp-queue/Cargo.toml b/cumulus/pallets/xcmp-queue/Cargo.toml index 8dde44ca0ff43bb3764ed53be7db0110671b0107..9078d5eda997526b8f3ed7d9f118cc9c927dedc1 100644 --- a/cumulus/pallets/xcmp-queue/Cargo.toml +++ b/cumulus/pallets/xcmp-queue/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate diff --git a/cumulus/pallets/xcmp-queue/src/mock.rs b/cumulus/pallets/xcmp-queue/src/mock.rs index 2bf1e3c6425a629909e950c72826f5c9385bf4f1..08ab58ce816046336fcba0a2318bd2ff77227e5f 100644 --- a/cumulus/pallets/xcmp-queue/src/mock.rs +++ b/cumulus/pallets/xcmp-queue/src/mock.rs @@ -30,10 +30,9 @@ use sp_runtime::{ BuildStorage, }; use xcm::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ - FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, NativeAsset, ParentIsPreset, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, + ParentIsPreset, }; use xcm_executor::traits::ConvertOrigin; @@ -133,8 +132,7 @@ parameter_types! { } /// Means for transacting assets on this chain. -#[allow(deprecated)] -pub type LocalAssetTransactor = CurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachain-template/node/Cargo.toml b/cumulus/parachain-template/node/Cargo.toml index c66c96056b956f8ebb4cecff39f2ecf0269dc12c..0ef678c4cbaeafc7a603748aec54ce2129a95f54 100644 --- a/cumulus/parachain-template/node/Cargo.toml +++ b/cumulus/parachain-template/node/Cargo.toml @@ -14,13 +14,13 @@ publish = false workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive"] } -log = "0.4.20" +clap = { version = "4.5.1", features = ["derive"] } +log = { workspace = true, default-features = true } codec = { package = "parity-scale-codec", version = "3.0.0" } -serde = { version = "1.0.195", features = ["derive"] } -jsonrpsee = { version = "0.20.3", features = ["server"] } +serde = { features = ["derive"], workspace = true, default-features = true } +jsonrpsee = { version = "0.22", features = ["server"] } futures = "0.3.28" -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } # Local parachain-template-runtime = { path = "../runtime" } diff --git a/cumulus/parachain-template/node/src/command.rs b/cumulus/parachain-template/node/src/command.rs index 6ddb68a359a786be617e384b16d7292c3db45a88..82624ae0be59e3159477b6f35b95f86177a35754 100644 --- a/cumulus/parachain-template/node/src/command.rs +++ b/cumulus/parachain-template/node/src/command.rs @@ -1,5 +1,6 @@ use std::net::SocketAddr; +use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; @@ -183,7 +184,7 @@ pub fn run() -> Result<()> { match cmd { BenchmarkCmd::Pallet(cmd) => if cfg!(feature = "runtime-benchmarks") { - runner.sync_run(|config| cmd.run::(config)) + runner.sync_run(|config| cmd.run::, ReclaimHostFunctions>(config)) } else { Err("Benchmarking wasn't enabled when building the node. \ You can enable it with `--features runtime-benchmarks`." diff --git a/cumulus/parachain-template/node/src/service.rs b/cumulus/parachain-template/node/src/service.rs index 830b6e82f969190b69425bd59a939c49372bf9f3..4dd24803e9b124d386d4912236ae1abd42229802 100644 --- a/cumulus/parachain-template/node/src/service.rs +++ b/cumulus/parachain-template/node/src/service.rs @@ -40,7 +40,10 @@ use substrate_prometheus_endpoint::Registry; pub struct ParachainNativeExecutor; impl sc_executor::NativeExecutionDispatch for ParachainNativeExecutor { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + type ExtendHostFunctions = ( + cumulus_client_service::storage_proof_size::HostFunctions, + frame_benchmarking::benchmarking::HostFunctions, + ); fn dispatch(method: &str, data: &[u8]) -> Option> { parachain_template_runtime::api::dispatch(method, data) @@ -100,10 +103,11 @@ pub fn new_partial(config: &Configuration) -> Result let executor = ParachainExecutor::new_with_wasm_executor(wasm); let (client, backend, keystore_container, task_manager) = - sc_service::new_full_parts::( + sc_service::new_full_parts_record_import::( config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, + true, )?; let client = Arc::new(client); diff --git a/cumulus/parachain-template/pallets/template/Cargo.toml b/cumulus/parachain-template/pallets/template/Cargo.toml index 2318375fec9c8d338e9e83ba12fa7b65449c41a9..04858a161fa43356e0082d99769fabd850be26a8 100644 --- a/cumulus/parachain-template/pallets/template/Cargo.toml +++ b/cumulus/parachain-template/pallets/template/Cargo.toml @@ -24,7 +24,7 @@ frame-support = { path = "../../../../substrate/frame/support", default-features frame-system = { path = "../../../../substrate/frame/system", default-features = false } [dev-dependencies] -serde = { version = "1.0.195" } +serde = { workspace = true, default-features = true } # Substrate sp-core = { path = "../../../../substrate/primitives/core", default-features = false } diff --git a/cumulus/parachain-template/pallets/template/src/lib.rs b/cumulus/parachain-template/pallets/template/src/lib.rs index 5f3252bfc3a70aa731bf572493a3797bfef574bb..24226d6cf4068d44820875540d8d209930860ca3 100644 --- a/cumulus/parachain-template/pallets/template/src/lib.rs +++ b/cumulus/parachain-template/pallets/template/src/lib.rs @@ -32,7 +32,6 @@ pub mod pallet { // The pallet's runtime storage items. // https://docs.substrate.io/v3/runtime/storage #[pallet::storage] - #[pallet::getter(fn something)] // Learn more about declaring storage items: // https://docs.substrate.io/v3/runtime/storage#declaring-storage-items pub type Something = StorageValue<_, u32>; diff --git a/cumulus/parachain-template/pallets/template/src/tests.rs b/cumulus/parachain-template/pallets/template/src/tests.rs index 527aec8ed00c058e5a9329a50d60267d7f9d1851..9ad3076be2cc9927063e1b50c293cafadf8f3361 100644 --- a/cumulus/parachain-template/pallets/template/src/tests.rs +++ b/cumulus/parachain-template/pallets/template/src/tests.rs @@ -1,4 +1,4 @@ -use crate::{mock::*, Error}; +use crate::{mock::*, Error, Something}; use frame_support::{assert_noop, assert_ok}; #[test] @@ -7,7 +7,7 @@ fn it_works_for_default_value() { // 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_eq!(Something::::get(), Some(42)); }); } diff --git a/cumulus/parachain-template/runtime/Cargo.toml b/cumulus/parachain-template/runtime/Cargo.toml index e4575d196c799e82049ecc69cba03174c16e4a88..1873bd0a23ebdd55a4451a9f3936875a2935001c 100644 --- a/cumulus/parachain-template/runtime/Cargo.toml +++ b/cumulus/parachain-template/runtime/Cargo.toml @@ -20,7 +20,7 @@ substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder", optio [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } smallvec = "1.11.0" @@ -73,6 +73,7 @@ cumulus-pallet-xcm = { path = "../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../pallets/xcmp-queue", default-features = false } cumulus-primitives-core = { path = "../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../primitives/utility", default-features = false } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim", default-features = false } pallet-collator-selection = { path = "../../pallets/collator-selection", default-features = false } parachains-common = { path = "../../parachains/common", default-features = false } parachain-info = { package = "staging-parachain-info", path = "../../parachains/pallets/parachain-info", default-features = false } @@ -87,6 +88,7 @@ std = [ "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-core/std", + "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", diff --git a/cumulus/parachain-template/runtime/src/lib.rs b/cumulus/parachain-template/runtime/src/lib.rs index d9bc111fcef7f1932d4b52e3f0c9f42d0dccc6c0..cee9b33bf37cca9cab905223406453c5424a5894 100644 --- a/cumulus/parachain-template/runtime/src/lib.rs +++ b/cumulus/parachain-template/runtime/src/lib.rs @@ -107,6 +107,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. diff --git a/cumulus/parachain-template/runtime/src/xcm_config.rs b/cumulus/parachain-template/runtime/src/xcm_config.rs index 9dd08dc7f3ea570f796a43915d290b78fa070d3c..b1230ba1e5d4151f12776040c76437b29152b468 100644 --- a/cumulus/parachain-template/runtime/src/xcm_config.rs +++ b/cumulus/parachain-template/runtime/src/xcm_config.rs @@ -12,15 +12,13 @@ use pallet_xcm::XcmPassthrough; use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::impls::ToAuthor; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, - FrameTransactionalProcessor, IsConcrete, NativeAsset, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WithComputedOrigin, WithUniqueTopic, + FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, }; use xcm_executor::XcmExecutor; @@ -44,8 +42,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -#[allow(deprecated)] -pub type LocalAssetTransactor = CurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachains/chain-specs/coretime-westend.json b/cumulus/parachains/chain-specs/coretime-westend.json new file mode 100644 index 0000000000000000000000000000000000000000..c79fd582348b0223cd4c1de71b94075751cfdeb2 --- /dev/null +++ b/cumulus/parachains/chain-specs/coretime-westend.json @@ -0,0 +1,71 @@ +{ + "name": "Westend Coretime", + "id": "coretime-westend", + "chainType": "Live", + "bootNodes": [ + "/dns/westend-coretime-collator-node-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", + "/dns/westend-coretime-collator-node-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { + "ss58Format": 42, + "tokenDecimals": 12, + "tokenSymbol": "WND" + }, + "relay_chain": "westend", + "para_id": 1005, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x0d715f2646c8f85767b5d2764bb2782604a74d81251e398fd8a0a4d55023bb3f": "0xed030000", + "0x0d715f2646c8f85767b5d2764bb278264e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x15464cac3378d46f113cd5b7a4d71c84476f594316a7dfe49c1f352d95abdaf1": "0x00000000", + "0x15464cac3378d46f113cd5b7a4d71c844e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x15464cac3378d46f113cd5b7a4d71c845579297f4dfb9609e7e4c2ebab9ce40a": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0x15464cac3378d46f113cd5b7a4d71c84579f5a43435b04a98d64da0cefe18505": "0x00a0acb9030000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x000000008277f47279c4", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da998af9d0b9c163cc7caff71526e1e20bf4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d834c58cefa69bb125bbc8bef14e90eabc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c": "0x0000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x66763d0040636f726574696d652d77657374656e64", + "0x3a63": "0x", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058544104aeba85d4125110284d291ddb4a3c50cb1bd0b174da063483a55a47260ab964ae54d7660813796247c0bec7d0f1b338b9c2280c6160568412ae63c3a7815a544782b1d1ea96f47ff926f8d0da2664efbdf7de5b4a99640afb1340115a114f7dbbfcdd2d91be3a3843eef26f979f4522f5a9bd54f4a9d7cb0d9bcee28fe9e3738e1937a9166f6707e9b5f46edf77febca5077e3ac8b3bb558280dee92c0ab13febf7e59ff5341265ce93916b5180787e58439561514395b06f6fd71165cdb70f35547f3b778f192b9116a00609dec11fd56143b1b7f3986840761ad9a7b3d7ed94523adba39a76fbec6eeaddce22ed7e7af7937c0d995e7d7a25abe653b2368cc9727e93533ee5800cc2c835da9e1f1baa6b28aed1f03c7bd850b307c7caa167672626a7fc39c76c4e0d40be609ccece1ec80d35793e3be8e286e267e7866a2fbb67af0d359d1d9c470b50238777f083339c773e8bed6087d48f26a03256a73e9d8a3f26c933680169fc348cbf9d5e6e18759086f3ce6fb19dc54a72c3caeea947358b3f6ac3a84775544775544f9101b36f67f1c7f4a88eead99c330c90994a7fea20076004d982911b568e4f7d3a8b3fa64f110809341a2dca4f073be4869ace5e1baa9d9dc5ae61d4a37adeb061d4798ce5d0536faf5e764f1d1c6a286e183bf5a2869a4f1da9a1a653076758d450fcd4599c1ed5515def93a57732fb265f43ba6fefbec9d790f6eaed95ac9a3fc9da307616854c6fb26c9f1ed551cd4c949cf2e71cb39e330910640e5a401a3e0de3e786b1b3f8a3368c3daa19090499831690864fc3f8b961ec2cfea80d638f6a4602b9416eb01ca7738d86f5ec2c823cd38c5c3be3e727eda9833cd38c5c3be3e727eda9832e76662a27eddb41be53cb38c5ce5f4eda97ecedfd5c3b13e8e74f6f71c8a43d25cb497b76904522e357a7f749fd4a52cdaca7ceb79c3d3bb38fb2fa4bc8f795a4723df51ecfe34bcefa92bd3d8af2e5a79779073b3843be53cb38c5ce5f4eda97ecedfd5c3b13e8e74f6f71c8a43d25cb497b76904522e357a7f749fd4a52cdaca7ceb79c3d3bb38fb2fa4bc8f795a4723d75bef33cbee4ac2fd9dba3285f7e7a9977b0831df29d6fc6e9f3992d609f22953f7516a9c6a72452c3a647755447755447355f6e183b3335393569cf396674f61d70ce19a773c3d839041983713a3b7b4f564c2cd4c8f33d9df1f4e7c9cf139ca73bb0219ea27872f3d4c4539d273a4f443ce1f0b4c3530e4f433c31f1f4e66989271d9ea078eaf234e609cb0909272d6055386501b3024685531930269cc880d581110183038be33405ac0898113029606d6039c06e80e1009b02e6c6490998164e79602cc0528089719a03530186020c4d530db025605f602bc0c2c080683ac1a9052726602748e00706c6c90c9816a7349c02358dd1a48593144e6a38ade1c486531b4e4fc0cc38dd716ac26909a73a4e4738e971f2e324c7c909273a4e28387d71c2d3c4a7890b98104e6838d9718ac2e90427289ca23851717ac1698c130e4e679cc838617162c12907273330139cca38c9e034c5e906a738b019607a9ac86832a3098ca62c9ad468c2d31482a63e4d5d3479d1f4a7e98ba6329ab068b2a1e94b93094d5d9cb434a1d02443530a4d58b01a9cae9ac0346535a191a00b09c468b22241194d5b48f0c7298b0461345d493006cc8b045f34016a4aa3098d26319afc4840869394a62a4d3134e5699aa2e98e930d4d503449d1a4a7694b1315303c4e4338c17112a2c90aa73730289aae34618145e1c4a7898a939ba6354d55c0bc70a23915e164048c0b5809b02d4d7b605d38d560779c8870fac1a98fd316b03c303f4d6a9ace38e9c00488290d263098ca601a83890ca62f98b660f282a90b262e98ee30e5619282c908a6384c3a30e5c0e4860907261b98da303dc174039317262c26354c6498ba3095616a81a904261898d630b9c0f4021316262e4c26305d3189b95f7c0f530a4c3130c970694c3530cdc0748509858bc245e2cab94c5c3a77893be7d6ee1057881bc405e2fe707db83d5c1eee0e57879bc33de21a718bb871ae9d0bc5dd73adb84ddc3a97880be7beb94adc2a2e1557cf9de24a71f35c3cf7ce8de2267167b8692e0bf7858be6ba70cfdc3297cc6de18eb95a2e1707744fb85b6e949be576b9265c2cf7ca03f9184e866739155e854fe17a5c0aaf791e27c2e3f81b5fc2e1b811fe83fbe03d4c1d669c69c66370357ec665f02b7ee5519cb614c6929ea526969c587a620988a51f96725862b394c29218df614985a51296b82c6d59d2a294a5b486d2194a642899a154c65214253f4a5f2881a1d485d21e253d4a6094c228b1a02446898c920b4a65945a505a41898bd2094a584a5e944c58f242c98a5215a5ab243594aebcac78a9f14ae3c5c64b8cd711af35906078055a1ae365c66b8cd717af3290c8bceebca27849f182e285e745c64bce0b89579c5713af33c0aca23cad34dc9ea324849e38a223d4c411124b593c09f184c7688a9215444d10915164c5119a2335de134a583c3d71b405c626e47274550446384611202317261146568caa14f131ca62a4c5088bd116a32ba3128cb81c19717404d195a23ddc15474514e1196243488c100c457d8ab2f02a447f96b03a1a96bc049109a2419c23d221aa82e807222188862082822808223b444010d588b8200a811702a52e2d322d168c6430c2c1ebc304c6d3e2ca706330aac1dab168381a426588b08868445e8e6a445b88ae86b284c21071212ac18333d4c6119c232288a61061116241e805a230b388a01c826a1042230485d00f426b5a68847c10c20aba21888720218286084a130443109a201b82ce04ed10e4264886221d8a70081293b446919b2124b818382a5c6f966018925314c6e6d9a6e8d67c736e6cb991c5c5e8d204e9b004a66313c4c2b7449118b72248ca4867cc1a9918db18e78c4a8c498c7ec6268eb25eb451cf9865d47264e568ca1195a32a631ea32b4654462ce3d558c518c518659432d2c629aeacb1ca48659ce29a19a93052234425c887a21b9ebc28c2228c4224056c83cb82284b51152d354266845c408078159211aa110212af846d885dc412442e2216518bb845cc12061269e119e294300d314a58862845b412b2215209d708a1089f08ed844e84688459a1192296582544214c21fce242c29584501dd71ca41590c020a9809402120a485f904e403201c90b12165217a41290b8206d41d2829405090bd215d215242b485590a8204d11b212d406d1164c663a1b9a0271575a7f5a675a6b5a34b46a50a2c20886a440621df72332114407c795d315477598d2f819e2124251969208ca0aa708d9790aa26b012683530f620c4e622c4dc1c404531da624c23c4c48846088b48454c22a445688aa10f92024035197d04a2885a88790361448088c500b476f846268cd20b442911e2132477184c6bc9e08af6041845796c068d9b0e4e70617ee4c510f37b418d960a31877187910aac2c9f049b1f40248c35219904d50202129a30edd0ca21ba1294d6138ed6064c6bf30ca6189cca7c607e83b23a807a23e45360495099a41c8093050eb05212e2e2236295a654638de0c482d787196f87844e0c0b224c6cb09a336dc9dcd088fcc9398a22994c6006b00db80362cb910c4068705d319a53698c460f2b334e5d3a3848612a0a0183822884c98703a3845528851885088764427c427b819bc2fba1b96c61051e1b4604283e373a914515174055194a52f96bae0d0883d0889c16486d0198e0db7a669ceb5b259e132420805a21d8878f0deb46410fa227442d4b6c5b667fbb389b1f1692db15de1416c58089920a482cbd9b4104a61cb626271218cd62cb9712c82d6b4ae08d1827e0852639406a94c100e4c5a2899095ac18f083242298ca0165c6d2c140e84eb082616967600cf00e5b4c00455b164825318dd1c5896560b5bcd2886a6283a3a5e98ed8d111aa333e0114b347862c612bc3162134a3034d9615a2108081007223a4c6896cec0b06c15d78b1804131bbf41fc42498cae8d77c5e362890c3b05ac07ae886d08d00ca72eac1437be386161a443b744501bd70f412ec0e6785bb87c087aa1b5c56be382e35d8d3d34b5e134466b05cf8a088493174d337834387d0173628985221496ca80fd3004674909d18f52154d59b63b4e3e38f1e03934d1b958ad314c2a0c1161e4c66d68fa21c88c111babc76806a52bdc4aa7c67130ba012ca3c8078fcf520bcec3a7048c872636416cbe2444219ab8040561f120a971fa033be304042c86262d5e1738b27435048df1fc783534a5697202b6a6e88b110d4c67c0d4785e582aec9d5bc57570374a250451318f984a4c246612730b14d9942381a9094c1b924c34217cc003554a38108139c3dce1010ee02001441ab0010316a08020407e680000d8a4338dd031810f02ec78a260039d18e07c9e4170bca6524e9068c0880848a4423062049cf4832552468c7c3b76bcc6bc5e621884241b58c244aa0a8954130f9c247122b5442a08d32b8c0746986c608910254a9820a0ca0329254a98208082d70a3c5e60942841e28112254810a0420855202c915292024b496dc008932a235521809044aa034f2f14d889922448aaa43e302225b501a7d71766b28125498c78a089d408541e40c281bf4e60290f963059620489912422bc94074b9834a06ac9084c3cf0e0935e267cc0c403a90f965e5ed88907a4a68c3871e201a9214b982471e2440a02f785c51d4822c237919a6ac2640244eeab0b1b911a416a040714f9400a04109014a1e055023b99f2402a040fb8b093aa25524da4425892c45f5b9624d9c0eba5853d48e26449922655244819f1801211444062a489d4074a928c109bf280899218d22b0b3b397a61612446905e57cc012348aa907c80a44a6a83a0d7950f245182244955922549885e5646f0408a0429a157156ec20401e18b0a2f6122c208463e509264040d185982c403234944f8aa25521c00410a021f487d3082120f9624591201244d4678a7d714762295a42a49132621548d80e4c64b0a2f6192a40a4993113e3052c544891323494478244d4678a2560f3ce50113251a30225585e483244a903c69f19452a20124554b46d880912a0fa49a24f960491223468c6e9787db93841a3bcf33906b34dacf1935daba31ba31a60893ab2f8ee338ce5faf66aeb6d9b6d74d6cd9b6e759ef76b3b5b5d26a69b7672ba594d6ba44b9569e9e67adc7dc2d669e3cd9329dddade6f6badbf6b4cc936d579eec596bbb5a4aab6d6bd9f6646bd9b265ca5c2957a9b55dab999976dcb6d932b36506bbeea363d7792dcab69d687bb57ad54eae75a3bd7db5daadebdaccd672edd8b2b595bbbba910b6ddad00b66c39cbb6369d6d2dc8cd69ad05396b9939e656c7d6522b766deb51d7b5abd8cc4df986ad5cd74aada5dc9452fa715ba6b4d66eca93a765b66c6dad96d9d619a42e55ae4d992d65e6ae636bed9cb36933536edacd6c9932dba6b59b76b4e575f41bbb6ddb6aedba6da51fed5a2bb3e568a5cc5c7976f735c9dd3d5ae6aee35a29e5aab5b67274d25927b5b5da5a69edbeb0295769d7516e6bbbaf56dbd5d64a9bda6eb6d572b4a394d63a9b562bd6a6b569d30a93a0bdee6e8ea3d4923870e0c0318e5ddbd66a2dedacadd6327365ae73f6e4b6d572d759bb76edb6d6325b6666ae67dbb6b6d666daddd556e6e6e65abbcad56abbc56e55cbcd952bb7654be9f731775dd775b5a3b4d5a2ad16a5fd7ddd5dadb5d2ae56da75dd59f2e356ab7e638bdd5cedf6686d6eea6adaf1b4d64eaf6366facd8e765cd71cc7d1da51ae9572edbaeb2aad1d534adb7237c4d3560f88e7791c672db7c8956b57b641ccd66b668ea3b59963a6cc1cd390b6ba9b2b8b2df2edda97d9dace72edee4a9927a5fcb99832739dddd336cf8e67d755dab5ba47db2d57fd51bd24e6cacdec2580f6a4cc744eee9abbcea3ad27da35ad5cd7ee2a1ddb7250100e4aabb547a2c8f3c68d1b372c775dc7dce2d608a81d830d35edae5dbbae2cd4ccfcddedd47577d35a9b6a9db36bcfd99f2b8afbbaaeeb68eddad559c5da1f336dd767958e9879daca2c8aac8104b567655aab57bd56aab5bdbb52ca89dd4dc55a99593c129794447a80393434bb6eb679ab957bd6aeb3fb6b6b2973b7ede6b176f7e4dab5b97eb3526b5d963f97ebfbe6ecb6dd5fb7adb5dad9b9babfb69c672767bb9bc5b6cc3c6480dadd3dbbbb999916a0766f6cbbd2e68d85b8999bc10c6a57aefd357794bbb9bb6b37a78f6a6464645439cbd9e69edd5d6ba55dbb9b52da5df775b4eba825802deaba5a6bb596eb38dbae0130f52872d12228a6223ae7b41cc7b96cb5185c600590c3e6985c9ddc7459aeb3b655abb575ceca6c6d73d6b6a5ad00749576d552ae56cb6c375b29fd68a594ed275adbb5abe57a6b5a9999adb5fd64c5d315f406b7079a30e180140736902d907a22a5c408094838b034c41680c0448911109624010104264a8c04a0444992291e1638f18055008b4047044e009860078fa00f942409c10323554a4640b201a90f9e9c04a90f962431b2240992103cd8c006989001c8a1a30a4913a9255220349120e40048397122d544040e30f1c04813a9274c3c906a620489930f3c903262c11414060110c10329273d923690a489d4132304b000030c02f081941126552278a089073928f840aa89d4074d981881c2200052499a544d3039c06c8154930d1869c2a40a89074c9428418274015412094c3a50a5048541009c280016180980d41412251a3002c2922421384112824b712049d592119a904203d840d508494224488560a46a4992262218f1c049120e344152e5c429c889d10d0e0013264d8ea63c40a201254a983499600224524d8e3c203565a48a04a9254a48555220181139a97292840429a48d2d58c224499507489c2c41d2a4ca4855120e7080092c020e0009493620d5440452a8091229251ebc9e2c91a2416c8191254c92386922f5c4c8074ba44410018911291090346152e5c448150952484f96485158a8453367653d6a359b714766f3b3b29acdba236c6565357956a97864365d4766d3caaa522bab7a6436adacda8a1e994dab59f711ab39b3b2a247d8b39af511b69acda86c568f3038b3d5caca6a668ff06c6665359b6d4778666535adacfa08cf66df91d9b49af48895d53cc256563deb8ecce68c3bc25656b3593d6235f908cfb623b339b34766938fcce66cd6f408cf66b3593dc2b3591f99cdd93cc2b3796401b5cf64e6c901274838e5ec67e79b48e4f372fed4f4affbda67f9797b15876ce20faa2912a13ec94dfc41f5633ae820174e11c8dc1c9ca27b2e7ad7b1d3b36dadb54e3daf80b885e09d0fb218e4732317bdb879ea9fbf442f669e7a17861e76de1110516c857326b95c2f251732be9492c4d946149344f126b5ac98e45e52cb75e3f54a6a3baf97507d390e7fb9c670cea1250a68e9f5ba62107d891e818b0e8af7b5a6a15e6e1d1c6f4c1df9e73cfe8608e4c83fb7e210ea9f834261779884248a4d84844a1e8e2ec4e59d874aa187235952b9c49b445259fdcbe72dc3f0ce477abd3caa93e6b7f8e3e55424d2bf39d2e50fdf822fb72fd18b5c74b0fe9c2290e99d47d16974cbf947a21f5d23a3cb5f14c1b5601a661dc7a56534ccba77c57b4bd907dd52f642b794b56e29a38028a0b76ec13454d7693b6f9d96d1509e5b07b7f0bbfca0f8912f21d4a7bc3ae72c2a605bd350a25b074551f416ddefd5a5c0ebb93604192f71bcd8c1e28365072bce78e5310fdd3f27651ebe15458f6a500cc521f33bcf5bee79cb37201aaae5d6c1d6b7becfe563183611f27da077fe7d9f77640986d755da31dfe51f5badeff2bb5a2d4f1c02137dba48ce350db3ee7e3f12a058b9934e9234f77befe5d8ecbeeb97cde6ddadea9f2e12b1e20fae69d9f255fddfd34f7203a261d65bdcc0fb8f2c3bd00798932c65778a3f42b722e1a7fb9d6ec56332772b190c5ffcd062d769548f175f683127a958f1987b479615f48d6abe775eda3193ceebf977a983b724e13d677103d87b42bea71ed5dfa5a51df3d63720ba4ec3acdb327c3bdbbcc580f5ae33dbd8e9e6edd05b1fddf9adcf356e3dec03b8db2a16b9ee4ec522ee32d29dbc6ec9a9adf69cc38ace5eedd4455954849465cd53af6190d97355d52959560929654d9e74be5555dfbc4522b02fadde9624502d7de72c12b15d8121a43cad800d9dde7a5457f107259f742477ad53d0c5b67a54db5bb657f5bf86cc6f17e2f5522dfd74d0fa764bd96fd6b7926e96fd5271088cfd89fde92c12f9683fb949fb64e0e8b926449f07990266be325ffdc8d9bb9901f7e5a91fddb2ffe5cc3e78bccb8f9eb2c697f3f872fe78795433fbb0e0fb296bdc9cc797fd9d4f71c8cbcb7e717e3bc82210023cf38e6ed6bedd12c8ece776673fefcb5db73cf2e60a206de02e47424242a2484a480ed21009c9a37a1c9594941c9ce1789594bcc51f4a2e57abd5727086aedbea4285022d079902498e2324ea3cc84676e4ece7ed6e397dbae82eea87dbec96f9b59ba0f8a3e5aecb3f5e7e3082708ad38bc02936c0fae62c4e5201d6c12e904df2a4a4a4249bb4e44b499e94b4949484e42d476ab5c069e6e896f35f4e95bce54a258f47f2f2e5ed9b0884fa6b3e92b78c8e40b045fdb45a60ab7593ba45be8644e0491e8127959d74f4ba01b6c0248bd54a4a0293922e18267912f91a52e4a01739e820d26d521fc95f48439b1b97b3b8c1fced6391c89391bbdcc85d0eba2ee848b7fb5926246a799293d9b7383cf3935a975ffc010e41bafcd3bdd73df2287a74f98d2e67a761d58beed6a661d523b876cb2d65ffdd52f6e5e646c638dce538c80e50c3aabbc859a66195e86260a45f7278383c5fbd0334743130f6979b9bcdcd57e7ec3494dd72e362609c5f5aacafbeb56928975707699f860a0afa3e178738fdd7a7a13eaf6077cbf9bcc51f9f779c869a5e1d1cc7717416a37af4280dcb69e647a7e28fd1e7e50fbafc23d9711a561df4bce979f5ae83b3dfe68d6a70c24a28e3773cd74a88d3c5cff778ae757183e5e6a19e6b5d0295e0a60430255c7dd2738d4b9fe782e7919e6b5c7e785028e41c64100c59ecc852f69dcf5b8e3ffdde2b8fdd705e5276ddea8b7163d0750e82e1bd1c9bb7fad64dcfc130f43c055870bbafe9fcb37f0de97cf3201d59523f5f7d96932c653f7df653fc313d8adeaafee99c48e409749783ee22ed9686815e1de4c4214e0f8a0e76384521d33b9fa290cda7e87323b96193acea0fbdec02893f5a0eb2e8224bab77f976cbee379fb7ec7eba789b0811bd7321422e926513219d07b990d13b17c9727321921b26bac80d15449654f345efc8921bb6f9e8ba214965b55df1f2b72ed57c1074f1f28720e8e210d883e46bc8e79e7f24edd3b0ea5ed905faeadf9886f2c859e6a88371f30e3ae8609c3fc5397f75ee0803e354e7fc25f5d3728e30304eaf1d39b93bc2f855e36fe4992b8fc9da4d99255ca0c5ac6a9f3761e0c4a07840368b31f9151e312b3c626ca6db2d0d45fd34d4fc31cf5fd23fdf72be7acfc941fbe468397d801ce534f3d5271ab2e390d34c1f80f4ea474f59e304f3fc65f7d56b43bd363f73e5b1ebfcdac822e3f5fef9d5b90f70bdfabc45b382e9f61eb18ee956dc402c42fa752a16f1eb2d3a806339c8913e4d9fa2033846921d7d9acea203387649a64fd3bf3a4bea0aaecc320de5392527a032ce325f2b4029603f07c1197ed786adcfb6875f6f9b779d7f5f4704f65f77a77f37f4ed72dbe6dfb5e4f639187a674520a27f4e6fe7e1053d8a76203b78f945ebecad5bceaafeae4037ddf3497acee28fcfe7ed3e2e8a527e90410ee715ddbbaf21d43da79e83f472b33a1589c09e92a575f0cce8514daf7590c5902c651f7ee3bdf2d8e8dc0718499995cbb931f85cc68d01f5d1a9579108eca7fbbca087b77dbc1c735d36a183f38a64597f7a78bbef7a2290cda7d3fb39783b8fa25fe7d5a3a8c7dfb7aa7fbedd6e59d55324f2f4d62d39cb3c658db3ccf3539f659e7358f52ceced0ad650317413aa86cfd37e4a5decafe7da9632ada5cf338d7bae6999f3ccb50a841f5960ece79a1637cf3e82cd79dfb272f7ca635cc871a48c73ab0aeec0a1c5388e9faaab00ac4c59d54f87383df5339d47755f2b049e873d957df5beddbdf2581776571edb7c23cb7a808e94756ec53937065d037d75b0c3beecdde5188b44e8b757928a666565653d77b93128b94666ce57f5976928a6449e9e92b30c8b3f9aa4e2ac7a667b0303e406b94190c3193d02390323d7b28c79fb5cc302c47310489d994aaef3ed20bd577c2a59cebe3abd200de99719368f71000d558ecf8e34b1b8a1e28f79c2384b1e3c06d03d9c46a3d162ed51ddc363ed0a68a8eedb19c03ea80b09cb7c1d0df5622bca23cf2210d6f3ec200d6b991a5a2c5736dcc41f81b8702b40f7e0d89fcd5aee1c2d7617fb60927907f5e63a4f184b1e513d003bdfce03cfb75fd050737e01be9deb583e3ccc4a764c32ed0038646c980b9c4546161730fb5ac93913d003530418a16898db3bf4161930fb2926a9ac9ebd6c9f4c0a71c5c373ed0a4cbde2e76905c1c9220366cf0d2bebcf7e8254fc613bdcacdf1a64760df502327ff38dec7ebabdf399a99253639b0187cca9792798322703610bc6f0eb83aef626a957bbb537d40b483b75bab9f529ba79d750fd5bf8511dd5b35964045d579501b349760dab5e1d7409a1b2facaf32ba901909b524abdbd564a2b7529901b8a6b57e43c75f6f082dce034616c3dd7ace8f9e9dc02e2ab6f3d380e8d1663a73de8eca08b7dcab383cc3e5a0e82b3d05b9779474896e00faafe202059f6833e3dbca5d587f35ba513ab07bdfae7cc3e3c6ff18e1659824e5d74ef32ef10c93274ca3f2f15979595d5c6570f6f390332bf7a548397ffbb53957c327f7321fd9b537148f526cbd9b7f3833e6f397f3a15816cde72f0be84fc148384cebc637a148deaf052cdacefbc755f6210ce3db29ceff977cbf99f83db2de76f2e7a1589540f3daa41a6c0c8352b73befae620831db2184474902ceb83b49f74dff216394521f5370741165b6439fb9687f749f7a183b79c0f3ab38f902c4357e2e39977800e7a14052f757086e0a5ce628ba4a23fc51f9b8bb7dc8654dfe2d0a43cf839fb1483ccac0f49e61d254852cdac07dd3a8b0ae00749661d9ed3eff27b773a8d7758e76ed93ea37ae6cd4d0ae8b4471597bb33e75570f8e63bdaab1879cb0140bd0a96e9455e8133ed51c5889409393df22a4b0e3a7b477b54e15125023772f622daa30a0e5286c35d2e370a72d7751c2ec341ca8298dcc865383c749757a1a4aceb7c2465a373d4659494752496e9b2ce953cc845671f698f2a1f29fbbcdb412352463a97c39dbc39377259bb1129b3d64752f6f9e8322307bd4af523523634e442a48cba47ca3cbfa0cbae83a48c529781fef21be3e82129b3d63f52464959927f3e521f5d16fa48caec0d97597f72095c36ba04a46c68c889481991739c2b91b2212527e2885c0297712e012953f2a892cb3c26120bec0629bbe14444feba4144447a8eef73a5a4ef033fef1219e108a223d7de8783762091755d1b766714e4eaee72231d3f8e08b4210e231cdd7505d9cff32838724441e188c30b8d82b8eefa735d4b442d9111ed9c2618c1765dd0290892e07372ba610452efe9c965ed4f4f1e0e1ca4c3c6ce61e10413b8ecf30926b81ce772d29fc2cf9f2c0ce6b2eb302758e7221a9df4098c827c02a7c065389c82a7279781fe24c19353e032cf3a3981d74987592287b59393d3042ee37c0252e6e414449d020924487259e8499e93fea4e449397050e04f2e33f22718acc925206530cf21813fb9ecf31c2e1b4926bf71a38d9c74a7a424cfa1a4e42f4fcae1492e0bf2a4ce9d5c87cb5cae63f240720a5ce6720a2693e7a0c075b80c87e77059e74f302424d2919e1cc965d791a6cb404772095e2fcfa144ca92485992e7f09792bf5c16fa6bbaccba040e73d9e8b0d9e4e432ce9d4859939393a4acc995941ce632eaa4cb3cd741ca2698c09948d98b94bdfcc60d4722654747fe2265487ee386bf5c46e42f5276f4f223978564143f4d67720a7284429e44ca8e48d9912779183ae9b2cf49522694c3499781a408fc345dc82970d94792c04fd3292065139058e613e81290b2eff3215236e4a0040ebacc233b0049d9073a1229fb3cf43c74d92539e0a7e9482e232215c04fd361a4cc89c89b481949ca4897e03a1329cbe14e9232ee824a1a73b25820c384abd86de2ca1726d408234c95266255ae4719911d3f4dbfa4cc49c741323f4d5f22993eb12b25193912297b15f911299bfd80831775f0c89086ce0f31a399182952788193270d3c6462462e0b228bf869ba11292b22993e554096b06797b9c8919fa6474076f4891d87cb2819023f4dc741ca88482c335625c8874899cb6f905878c4aa50172265412d7791b2590c67d8381303a032be102346798a3f38c0e0450c203869c4aa5077b96c2407c04fd35da4ac45327d0200590ee0d9651cc9839fa68b64117d62973579849fa687a40c24b1c0625536ff4859017600028c1aca78e28f9a984762f15895a53767e430841456d4f02256a59d735947c2f8693a47ca3692e9d30eb2e4f1ec324b3a3f4dafe4489fd8d967d4eaa977648153871f6ad82c6ad8f47eae9dd9e187a8ea23f18e4972c3a847358bf3ce20fc5433ebd959147947df9077b4d3dbe6cbfa3dc35b8d5cab52e7d9ed73ad8a992f3beaed22159f6b556a6080d52b9fa9f3430d63b236ac9d45aa99f5f676bca37df6d4f996f3d9a794b17e7b1845a3ba631fd6db3df6c1df6e2f734161c3b86165d14f9fcf353354fc50c3a6d73b9f2f378cbdce59abf22510086353a7d429a5739e524a29136d42058d37547b1d1baa74262a58df1e3614fdf632fcd23b5686cf234c29f35c9352c697aea74ec1734d8a9c075dd46b43cdbe6c9f3e874c4df235a4fef4fad349a0d168b4ea4d96edd559145292f0d52b4935359dbfe47e3a9d54407e660267197ecf09cedb35d147399fba00c62094ecc00ee7ad1fb28f26f3cf5371ff44c805d2705efae2932ddcf34fc216fb6832694fa5c14f6f679148a5d1a62864a4fdf450c8a4fdf49e8d07e4ef1aaa3c9a80cad83ebda4ce0dab606ca7cece2291f6294a760d638fead99c0998aeaeef0454c6e9ed94cee9e00ce924bb9ef5a40299a91cb96165d74f6cc1b8f97c900330820c82916b527878ae49c1fad2f5ed0278ae45d1f32dfe28c7e95ca3614175de5e1b6af3f60e6cf147b74d9101b3df1c9ce17c708a9b83dbad9a543f6ac3aa0b197ff3292a807f73167f70c3caf0a725cbd9dbaef328daddb27e59bd8a3ee72cfeb01ed520c822254bd953ef6ee9dff9764b6e1809df79c90d2b45f8cecbcdb9616508df7914a59723a96401734eef6a7b37e7f4aea968374be0eaeafbf2beafeaf52a208af3097638390e08d5cc6a52158a20e4c8771d731c1aad73b03af5c82654fc8746fb2aa1ef9cd2aaf03b22227cb7099506dfbca37ad5f69f974d3eafda861879cf97a834f8c93b6af548661dd54520d637b2de8ebd49253bae0c6a1db477c68981d44170f6dd2cdb3b7a69734e9ba492b1d3ae54f4e7e49c0a8973b06b58e739774bae738fe3486e58479655fd54d3737a4bea4fc657526951beec9c8a0b1425eb9f74df4d556f079d23bb1b725e761dc979d9395547454a946ff264fbe94fb877327bcea3287737ef2e3fdf928afbf3d6378feaaeaabfbaf712326bdfbeddc9f9e67d9f74e7fdd63b671108554927779d639f776a92dca5dd91e57476ce85d02f9dcc1e087f25a72839c5deecd3e7f605c8dd903fbb3314588ed3856cbef9e6b1b3e7ecf9e7ccdf1dbd27f3b76d92ce3b189c21177ef3c1cf79bce7736e0e7e1e72dc90cd41d2e5f9146797ce99f4c8a186f1e69e7f6439fb6fbae7517482e0e775dd4ff72eb7b18e765b594703a93f492ad953f72e6dd6d15e2e6036c44fd6d1be80d910cfaca3bd8fbd3b4b18a90b19c3a6aaa5f5e9dd7d4d926a3e4752f0e00cbbbbe3d9b7e99b10585ac629eb4e5f5a7e7e29f6d184c79f619ff1163e9d99dc482a5676c436bfba11dbd250435f5dc95be7f1e5d053d97c5e8de5dca2c55797b18ff4c98c313b90d9a1cd1e1a2db6f904375a5553020c525e082246c5ca8ed8f4ab1b31762a563ac6f9d50162d3b77b7580988c4d91e9320ee853f55947c6a608e71b298ba24fd53952c6a6c8e693946d240fa64fb5daf492ebf019579038f3851469ae7022c6319ea1302788165a7883e70632621c632f7914711a2d36b73494d357aceeea0238b3d0271c33f099c188f7510af9aad76af5d4279fafa42db7ea1cb96423cbcd95501bfebc25977849c952b639bd74b07eb35e5a7d399d5a57321bf3dc12a683f5d6cb0ffcc6914b98cfdb6d8901a8d4de9275d00851c11d386449c2539f5e2b7046c66ee5cacaca1a13f3ced092b8c4f0a54c1309cad42087562502f3f933b40aeec0a13d3100951a0934a68315632f3ff0560af38979248df9c4d84bd95bb29c4eb982926b67daf8aaf9676838aab0610a17345a8c4919bbd592165cc81325108d16eb485a597d96223c2d1274451532b44185468b6d70e39b8649e33a1c1469a8768ef57c757032c174f0f8ea30ee96ee4cdce5ea1dcc0aaa176958750e38a03d9ceb5c80ebf89131a8f4a74d5be5ace33e702404114898344b0668edd8509db33bfbe08454d03a8b41ac57b7b37f4cf77cf3167f0c5d5bef93f9e57c32bf56079b27776dd9cfa7a653a75ef515dcee4b08f379d0e937f8025914c2f57990e45941d96fb7d94c5b3c2bb04eeb55127ebdaf21746a73eafcfc6343f17876eff278ff36b7777a19a717358c7d68f3aff32952758fc43bd8bb3b368c9dbbce3b9864b256b20c6b20d03a3395dd1f2b63d9d56a0dc579f52e4e43b557efe43454e7d5cb6ece57075f30f3123cd75c08f4205dc3dafbc158a977dec5a935ac7ad931c07a4776722600ba1aedd161f553f5dad58c8cd6bb5a87d5b0ea1a182df912d234da53b7a2d17e8a2c89fe49ad834584d9a8bd2595587f2980df766ebfeac5183333e0af9df8837a3f48434035e46ed9617dedb06606fdd5bb2f667cf50e4c43d1af0e4eda93b6fabedacf5c6910f37cbebdf3b9fb6a7f59e75a96352f4b4280639e73cd85329e6b2e90f142e85ba76f49ab2ed887f5ea5d1dda03f4ea1d1eda63c6aa777b688f9657effacc0c3aaf0e6efeb2de8139df0125be755fd667ac63e07d59d2019d5b9931d07dc6ece520f4ee961d58e237af0fd29e4c6bab9cfd8b83cf5f9cd7a7ff7280e82f8ee44024a7701620cfebf7cf8f6afae19d326320f9da482a57d69fb9ba20e6f91457561b312b33e63995ee23a770595959fd2c22993e8398dd9fe71c56dbcc366705381d9cf367736200b24f29b4d66b7d4ecedacda973d65a70bb483388da3558af3fa866d657df48a45aeffcdafd0438bd631f938441cd0f358c7ac73ea83310b3f28e26b9611d9dd306ca0e5a3a69e77473ba39f501741b75ba39a5d42d52c3b6f6a1dacb76f3b9f9abfafcfe57ed6173cb91480ddbc8daf10e9e5f6f3b01b657f6d1aee9957d4c926a66b5e51d5c79c79c02b2f5ca3e3667b7ec635ed030ebe67934cc3a8bb40c656188d43d3eb75e00f6e1bca373eb3a1a6a6c58e7d607f0d43953f7e8faa92b5f61f718eaa7cead33d69717bcf517fb986f41ef2235ac736a43547be7d46508f4dd143b7f712f8e73ae9d59f35c3b53c30f35acf3edcef748a48675dcb5bc8377f07c9e33db940dc836683a90efa73f8b3f9a32ed9e44c0cdc16d8a3f3eee8a6e1d042f90d997a17f9c33e7ece12db9e7da19345f798708e4b68b9773efce07c1ce411af2ed5ce49f1d5976dedd66b274b0c3f07eef1f4841d1992c3b2a12a8488942fb266148523999bde76507d66023eb9d701d345fb2bc478664c9208336ec6e835ec12dec6e3b8b4186886449c283ce21c96e45205fe81fffd4e7cc2495ec4bfa9fb328a47b909ceabccb6e487be779148dea1938eb0f208330562f8bde7a4359bfa0a1fcad179da1bd75247b79bc2d7aebe390b5ce352a603932df00d90eb90238d053493bd5ad73d69fb30ec2b8f90038a8b21b8f3414e70568287fce3b6828aec980e639bf4043d1e718cf737700cf3937d4fd92e770c3ac473512d897fb33c178aebfc84f4fa6adb1a1a8733ed450d539ce3967268e9ce23f144d9a38df43d8a194723b7524f6c14f3d897d00a9d5867140a50a942aaad4c1aa7244157c624c9faa7c13379c30a74f1a68bc89317d6ae70e0a1b5649186c7af74c7b703f95e37b3fd762a0f2b52b47ebb59dc71ccdecf9f621580168ac0cff86cc056d64d8304b86cf353380be92430d9b5c7bce31b3b30181136be4860595e3d3ae84e918ed3cf521ea5d680170b619bb86e7da0f5ebec93cd77818e361cf351e5e105235e5cf973320b62f47f062c4b3bb669b91c279aeb9d9f333ce730d8731afe3b976439c2906f996d8f354479e3dc8f7a785a7b26a7d598545154564beac424306c4e2cb2a2d58d82f9de4c981021fea7ce984873acfded16895467bce7a76f973a300e8ed1daaf9d5c17a6d25cb39c521d49b2cdf8cf95a7dbebdf3be86d83b5fdddef9d2c9ecabdb4bc5595959a01df341f4f9fbf7c10eedb577beefd24f31887f7bbd4f882659d5feed43c0bee50c32b3a8a5e44bc874ea41a653b20afc1a98afeab764d9fe643ef5d9d3fbf4deef2af0d96776361aa08f497bb0c38f7df8f89e9d39039e155477b10f66a3c7369fa657b2ecbedca28430c33baa8bf5398328215ee00af82b5936595afd937e109acc9fee0d7d2318865c83c1cf53b9fe49b7cd0cf869d95f6e4f7d8a4dc6e9e014a9c6094e3a1ce70755a5d1784fe5c1ead9f8f0cc60f6d1f3ec3d7b708cfd9b1954da3310d6c3755dd76ddd7574765d6761ade99b9da539208cec4c0d555630d45025d34f57fae9a119343f7d6ca8f99399e606ade79a992be0730d86403f840432ed71a4478a33ce1083258d9a184761478d12f62891060630b1e9946bc366756f58ddc8d27ff38e6d506d185956d19feef95043811cc87d1f5972dce7d441eece07bfcb0d9b6290a4af0df39caba2ff39389b276df183b086d25122fd9c64b9b975ea209d73bbfcdeed34a8b24fc26fceb7ec9e3bb64155646a1875cebd611cd95e6f399f6a6eb78a4ef10793961cc3a186e2a7ee8fd450fed475b00ffad499fe9bd60c730f4a9956e7fba43e7bbd42c667a7949def3c8f2f39ebcb49a3e349c6e9652dc7b18381cfb7874349a29a3613902f0e6318c3d5b70f1521cd0ce67c334e9fcf8d013bb740662a79cf5767f6c13e6677b48747adb51664b0651fa5937df4b3835de710989d7b0e5a07ed15f27177bc1d7daa6eefd7fd68ef728cbb42c0a73e7e5367431dfcd16439ab218bd6f6d6dd6d6de85d286cac5b75ee6d937dd0670741ea9e832f21e58ccc5bdf66784b36f1df7cf61be95da74fd5bf5b449feae604029f7b2414cf3f8f00c73c928df52a3ee08a06b18f84d1a7ea3ce853f54e2442dd92250713d0468e757e458398e79e97b01f1deb5bce860809fa910d9ddc93b6ca087cc3f0e54bae7df50e1aaa2c80021aaa643c17682828aebdf0e7ab2780e77c651ffcec53ccf1a555011a8a7a75de53bb6bb3e7c9e79a0b40bc0b6b5cb8fa3280acdffadc50d54bd85b6742f27e16eb543bf572f472f3f6269b92536315e32ddfa9f6ea54dca0e9bbf0e7cbf1ed5089f4969ca2de5efa5beb61d19cd46706fd74031ecfbc63523620adce6ebe3768a765cc7c3b2d13e7dba9ab86f496ece373cfabc800ebd5ab48a49d7abd95c77fd4bfe9168036b42211fa390d3da4fe7d4ec13bfd2ba94fdf4409740ea523a174be39e7e0165271483b75b00ba72881ae025666ac732856666cdb1ccae69c4775079d43e93ae89cab93ae007e79eab24d2c43e5a953eb9f6f413ec920dfee94ce37b7d7ea8b5967f6c1f9465a7db1cd4b91888b39b8f2dcda3b63de6543a581cb83f1732b9cb3019df6c5ac5777393f9932ee92ef410faf90afe55504325d28f4f24e29013d74d1697897802e7a149d975fe896b30f9aee72f97843679101937491238c5f561a6d7acbc1fb02453240fc7daeb5b086cc9a677aae91b96a01900dc1fb6a89a2287221785f60ebbe4051f4a8b622270e119d8c9cafea21df0338b691533a52368b7da415ce67cc9eb9e24212f4a8b7a46cb3ce791b6965ad6f2eb3ea7c23af3cb6f995c73cd2aa23af3c66492b571ac4aacc62d6ad731b05c9a908ccacbfc06a3576b291044bbe40b7d6e7245fa0256765bec51fd4a7b524d3a7ce6aebce0a904918b99625d0772d063dcfdf851980cc54f268a8da50ed72ab0e62a30444b71aea22d18b8a6eb8b8398b1d885e7443073bacd373f60e64506264918e8d35e486e22f02e5c8438732fa91b358e4c84352d6f2d13a8b1d8c2c047aea2cf8b162911b1ee4e115bd75a1dcf0965bb102462efa768bdcf02052d6722267f6e1a00d7dc9652d231749d90d0f5b976342174ae846be74a118f90d27ba5064a1df70ee96b3e7e18d9e6b2cd47990c51f9cdbbbe47bebdc15f2bd752207c12956a0e544eeba505aeef2a02b6b79908f57d6f2d159acc00d37f2a826ba32972f7978670ffae720175a162b200b72d1652e1772168bdc70235236bab874a1c864a28ffef28e095d283259c831d795c9dc953ce842b90ec51d8a92cbaca020b99013b9acc865385c36e4a3158bb43cc8add8c1e83748d92ce6f2a32b9b3117696f9196f52012cae8410e7221152510ba110945f49673e24616b9e150427779d19589de22ada044e04b1d84e414230fdd455a7db12017afd5171b997d58b2e4f167ae5a1e92565f2cf4cd83aecbc72bce5890e99db328819194852e92565ace6623a79458e77cf3208f6a902c793ce71fd92290e9d4b9bbc4fae651744e31f2205236632409e545ca8e5c16e44beeddcec33bc5c8973c822b2bf2081cdcb89479a683b57129f39bdbaf3f06cd73133f918c6551cde227cb1a16d08c0134a64f0b5b5a183306cf1839637808437b5f1d39c4fbcde77fcff579e6c3f5f9cde9f7b32884f9fc66002ab58a051bda6fcefdf6602bb4f765bdbb2f4b0af9cdbdef9e452115dc81430b452106a052fb8d0e56cb9748d98cc92eb934bacc979446d20ac9454f22ad90d8703e3a9147f5bc63fcf4a07be5b1207fdd2b8f85eeba562e27f223d22a74a220d2cae52e17dde8ca66b11ba49516c9864a839f2a79565072cdcb9f9f6e9d7fc68aee95c7649dcc65571eb30a5df41b1ec195e120adc2d0895c6625e444e495c75a3e445e79ec066925445e792c24ad04915334880579e82239c595959595152bb9e6a5cf4fb24a24c24dfc243930d2f9d29e69f34ab8cefbaccc7b61e82dafceb48757ef4409b4dce523ed31c5e52df22a8864b3f92c569d232d39c5911198c5c68eb5bc5a8d9d6cd26823095c235fd65b6ec5632ddfbc14e137f265c922e3f4f2c84f9fceb4878b84227ae8d5bb99c1247d132bd0128b84de22652d0f5b62c8e339875504b3aec71ebc7c79eadf567bfb5179a6c0a4e1e6a1539108f50fab8ca73e1db4e1141b30c2f873deaaf13907bf16814c1e6fc1b3f8f91643afc3951065ba3f74cab498585aba13c8082a2bf0a006ab01539899d3268a33a09001ce1a7fdad89400238276682c50384fd82e896d03c36b17d3ca5519ef4650123d5c49d3a78a324acc62a002062d59e09961882c98c00a9918c3c60b0a2b9248a1052d65e4b0700496181ea880c184156a9851bf4079a603faf0d0054b0e3ac490c59675c21156c8c040e58b10b10d882e7e2b630d5f120616795280620d27aecc7a90f2849a2fc4f041e20a0d2d35ba2ee401ab33418b28070c47ad1726466fa8088e1e387636f1c7365a137af8b8ab5d1266ba24462f7d42ef82226ab50a3c21065680a1f44d126b420c92b0dd1a31e88225e49810e59bd3e7823bd616b50281522e6c81e50a22d270a266f4c21938a410c68f963d21204a63c80a3956d4792ac17e7f62376011b1c5eca1cf0f5788e0fc308688e3380ecc05377431451b50b06046192b00ea689f0a30e8f0051e38410c01688fbde39605037001a286374e4ca14495a42f64b4f451a28e0e637868586b6d2ccb9fb0b3d67aa8a586b7564800dbb66ddb9f0068dbb2e0388ee338306a360a0c3923c7e9e1c0e82e09b734c41989729033bbda308ee3ba3b35ac5d68e70f65d31b18672678c41cf9600124860f7368501344cf1a785c90c306960f6748e0b5b9e10934c260830f123eb4e499028a3f61d4c892a13363dbb66dc3439fb8a378a0881c9006167a5e2053c2137c92b0b2020e538a4083821577bc32267863bfc811a4c4d2eccb9d3743f8b132069e35e42c11484c135eeab061c61a3266d8b66dbb02a89ff8bd2b5a376e9b1795853214e6058e0a6db031e20c375dc4caa22b8e38c14f175df42c5184123cc6d869f8bba0e130ac3831049eab2e725ee8e285082804a1062a6a609c19e3470531b62f018b0a15eba1b5d65a2e4e6d7462806033840b28a8f083151ca49006971fb6b879a18dcdfafe30953e436357c60327b0060c52c874e902893eb1b2080b0c5957a408d1064f6c83b3049509c2289282a986258ac8418940808888e2824415668099d3264dcc6e6aecd9c0a8c551c2b3839487ead9b6087edbb66dfb03a8ab5c948f0fcc5afb0015b6c840840f6bf4194301265889e1cd1653c6c8e3820564bff8c268c18f116c60e9b2e5ea846ddb74e80963420b6ab2e02286d8e6a7e560f5114e5c90854d1a36c8d0020b59a021254f9b2ec290f2a6081e62b6931326c80b1b4bd3c6f3c20faa68b2a1d5ca3f048dd21faec6a271a8284f9c3c73f200513462d90931782bea787a7a90636d51f7c34473840a615e80020713cc50b2620b186fb2f85153a68b2e46c35514319b1a4360a094c20e2778c1c41a55b0a8d1c3842f6eb408e28a3163e07001830b220c7c79214d1a5c64e9c10427a81cf1021524beb8e973860e73cee820461c3b1d7803c55518294af4e027049c9c91092fd80a1e2b7ba8952cec983f514bd466342966aa6041c5851b84e821884e1242b33045d056b861072ceaf4d922041d861c8ee3b82fdd6aac50c5586b6dd8d903704438c503da50e6891a117f8e2801b6edfb6ddbb6edca5557b72965fc36858d2a26111454b9a0a58d142e74a1020687406ae810854d1c1fda90e2849c20aa1637d02b3552a0d65a8b4219cf13734268adb5dd1d1004601031054d1648bcf9130131d8102184195a00c1c6c847866edb3c3836749bfd9e06301d00f260a140a50440324ca982068a1e4a1883c60c2ec2b0e9c2e4d065c1449b34ec0579007180c0d8580f57b3ab26aa54d1a5872a687001ce181e42383e0072f50a60a86922501436c260c1dd098918b7f96b805913468e0c6c8c61e7852e88b0e28b2c68fa88b1478b2e2a9f2296aea042b864e1e2093a67b00122043e50c9428f1d30ba24f1c795c5b487e39c03d9f09c57ce09381e1f38540f17c5588318175a46f040c59fad0a3096e6c9f19ad8289c2c33140e177dc29f0ae78b1c110d77010a344ab53cf1d56cd7c5e9c46c2bc809a2badd98469ff8c7d6cd24b238a2091fbab85025c8183f9499a10b397142d08585b302ed86155ca3d16854c6a271a8e8aac31ac114fd1686d63eeae0ae7658a3edc1075a973c6ab858230b156848d3c74f192cbcc4de688144e3a95590450dabb12f65ac2ddad8d8b66ddbe205236a3384e1c4963739ccdcb04697345d8081868a16845228f3c38a094de0f963070519c2c0f0bdf1628527064dce1531d858d3c31d3d4a415ea800c6891cccb439e1823953e0f1628b174250268a15a229b62d841378c8e20207159e80b3e445072e5edaa0b973441c29c49082c6aa8041e359c1eae250305d08c57a643801d00a73e09c2943c90b6118c1a70e9f10600552c27b13c80ed2e7698eb332c3735c0e6d050c115c14127236af5b0f2d84cec0dab66debea96c1986eb3b54d1f6b6763b88013a60c2a9a089347853469fc00e884187688e244adf5c6093a43789eb8b21d07049ae7c4e7eed4a6b8fe0b9a98192d5af890040c6780618217464c37f3810c0d4ba4b96244183d77bacd06b529632dc78518bc1ddbd1f190b8caa26a6a4576131f07dab6891963df6079e15db0f1a0d16853ac77f68ed6cb2eb4e468a65b33f6f828c6f413ff56f461ae6a982ddb057f041e243b667cc33181021f2c4e18e326893159572c21658925c228a289285e18a57867faf413f7889df9d34ffc6537f2b09161adb563b409bbcdbef9a10eed82072a091af6a440a676831e3a333437c470041559646419014506ba2853840b7cdcacb9f345873066108186952b63443b1c57bf9855f45183884f8d25383a7ab834e4805d121b0fb40c976d23c2f5d65a6bc3ce663dc1d5ac6460f305203d377861c64c0c0eb52c940033840c7b6841d4f1893c2d1ddbb66ddbb6c5d0ece9ea0d2a26c4200399335854819306ce6f1b1728fc4684f5849b22de0871f5841338d88926dee0c0a7ca992a72c8c3c294ac1e86f0e28a36028d4d2c71006b8a1d2d7cb4c0e28a930a56a451a38a0f3aa04007853a550d96d0123443d001038829e860d92a52ac49830e17658cd1c69db74c042961abfc8980892a69be706941cf10575e7edbde5ca1f01b4d0514b8a401031617c888d1d962dbb66ddb7e05345d1553c36fdb057cea126a709689335d8d891ac425b41c599bc5e6ed8db75b06d65a02b8912b269c91a58420a840e3bba863e68b3f56e44401478e10bf1dfd36c7dafbd65a6bc34e0c2d4260c4943c6c5c1db16789366b30c1c213213013a58db75e8394b09623428a6db3bf6ddbb6bd0a603c8fb9f8cdebb66dad4510d7861dba0616ce004db4a0c5092df4f1220f57faf1c282942a45b051458cab4f7449fca007071fb8f8c0420e6fb0e0236689326ac033854c8ce3b83753b83cc72131563fa0b5d65a6badbd801846a000841028a4a181c403c61425f4c1010d326216dc22be1c232c2d75a8c883460e6b2435a186cb19698461a68798058f18a27738047bca1eb288d3460a25d0b04620cb45940f96a508148e58c10e1a2362d688b7d6766317b212333891860a38cc59e30a6b54c2132918a1821337ec10b3d6eb9b3e7ddeda22b6502db2c0c842678d305dee0cc101ce165a80e903464c203dbf6d71c8a1b16152c6b8c62840806a40244c78c106cc161dbcf0504495315f90e1a54c9b1048d1a1b2118523a245e0428d24a40c91458c1bac4cc0e28d1a448831538585c3b9306f1823ce0972be3c91658bd915304ce893250b062b6cc041224e056f66f0df7ef3a2eec131a93c5966a8c1fa220b3d48d42143041453d8d046132b3cf85b4dcddc346021079ff1650b3654c819838b2b0ac872c20830a4642903cc1b295a820e502f50210826989022059c2b2b6248e30a303a4cb1e70436d61832c019a32dabf61b01388ee3b8ca0dab1b9cc0c2115b8cb0d2c61004d8e3041e16b2ec6051e54d1434383570180b40ff4a063354ece0c294134050060c35bc69618d26b8e8f2adb06540419d31b2dce142cc1458cca8b0010405202e69b858418e095d1ab5ce470f80fcd04962cc165dd4c04cf819034c9f2b96a87395c6145d1a597004f08dc6f5731cc7715149b4b1d6bbb5bbbc849c5614f4025a79ce1a2eb26eb8420525b050c08b1a5e80a344145f44a14366a313c3d000c6a2f187985a90bbcab4c7f882162b27b071c613719a48c0092b40c1031d3464044147cbd7e7bce3f31c8ee77a3cc771e3386e6eacb5d65a4f7c14e4785ed8352c015e7ef8c1cd1a686c5144ac2cfa25f28062683718808605336154f8c2892f31a88de435d6a803031442e0b9c28505c1183a26a4a1d3650b2c6216092a6fed9b30657cf81683b7d67990433cf4d5b3ccdaa38b591fad75b7d6da1678a04f3c81600bc6eeeb3bc7715ce7cef9c8f95011c7711c7742adbd1b40d09045adce15953e53a8c0c49b2e4a2071e70d2d0d9be58c77413776e108a6cd1cda9ecb1413ba206382164c10c11d00862e42bc30850a3429c4b8da11543cf79c96209ef3b0a1849e736f28013ce7b086b2ceb98eee31d268b428316ef3a186216d5e0e7d51c3aa6f2452c36a8f58181ee813bfb5dd0f213153dd66cd02a178f3caf9e6614375becda14672f68183e93923c6cd677799f698e1a0c61914ac4cb9d26242ac2cfacd47daa3481b25fef010c5175da410db9ce1bc71e1373fbfb93754007e7318ed51639bf3204b2503784b860dabcec519f9cbdaa38bd5b1d11cf1e5082a4694f5d98834b6e79a1164d4aed81822ceb383cfb521c6f09e6b437051c1684a29a574de491bb976a626c40f716d081fd80c33a594569252a674cacc8ab1614a2ff38e24234869656e582bd1ade3db8bf8ea68a810b8e9db79cdf74b50c4038521f8b422308bb543a15e8592573488b53769d5d3c12900766eb105b53a7d39bd4b1e33db593d67713bb7570215dc79931563afd7521bfec43a92c674b0aa97de532776162be0f4f3891e3d4a7055aff5be36672a575dacbbea629cf32dad8a342dd6b977ad0c20c6bd84cc326fed4672c0397f9131d66d1b49e57a4b9ee9664f3d8a7ec75da64f65f7b61bab470bf55ed558757a8b8cb1eacc949254ae3f5399bb271931946e308605be332b68610c487b4a9693365900e79591ab8cb361932905ec7da9e6cbe66cb6063c98f6e0bc1c378ee953e57c73eafdaa80e79b17e99cebdc92453cdf48191beb1ee94d8a6cde91b2913ed1ea0fe058117da24eede6a53fe538b65d8ed55bc2fef2a04d5f00ad8c4d75ab2f66c15a81cd2d2963539d72aca34f318e59a75648d0371bb0964abea75e9d9265754b9ed982f09fb9f2181bea541ce27df5bee76ce6a6dd5f40a9f1730a402a27f33d726abae73798f9b29b4cde61bdaa7b2ace0a3fd0faec7273ebdebde106371f825edf72d62c76e90da91efa148784ee89413807bd1b7a1587743e7d92cc3b66d9a4e7e02ddb7bd23d156d835656cdd936bb8b3a3330d61696781bfcfce6b40b3b3802a6be9dce410736f0405d0f85f178eb1c58b7b02321345459c47218d006d0dbb74c2c27c797ac878f7fc97e7ad85869e79c739b3c37e7e955dbf37ca686b24dd8abb62146be7d6be7d924b38ef9638a3a039ca9cf0982df5c06058a5567e5b1477664499dbddc806c4e876c24146fa8ee8d1c32bddd3ad23854abe1cf6fd7fed850bcf964a38d369ec52cd6c1bedd99f52cd258077b546fb769cfa2908ef2d39bfef1d2802e179d7da8a8a140676a28ced9610db5f9ecbf2b3a776720f839f5f0be38f75cdcf3d5bf3a5fdd73902930d58c5caba1cd3305ca7c75b10607bf70bbd5417036a97bdeb5e00c2d157d17e853af219d5bef3a8f1437f83aafd5710e7a9ff37b1f098473cfbdd0b93b7f9b403eebdef711a9fe9153d6cbea4e66bf39271299dabc7a47969b87a4e7dbfccd75695b61b660c6a8b8dc0ab3059bcf58140def4752c9a8bb2ec74030745d8e7d97bf9c3d778778e494e8f5c7045246d1a92804f48d2c4507c917e89d5b6f518848be407208e7dbe71dd74d711b50aef3c812bc65e82fcff9e59140ac830e921f82ee6d539f836439bfdc888024f30e926a66794ec16dfee7ddf9179405788eb177e1cca0a33c7b8bc7001a8a5acdd9d7211610a44128d944660f2a9f53614341d0bb5ceb063ba4a177798fc8802a12a11c6beea70b9875f98e080ffed380a8ef9c8a43dad52aadb01410f59db71864c9535909097fe9b916173023f31d905999a73af29d83ad566955e637661d65e77301b321369fce75e1d7915ef85d8e8942da37f22bad7ebc4e494a96542452a9e84f518e1ca1e8ab8a7ea33ee9d4f4e9f5be86941e107ff4d616d09a3e6bc0c4b141ec5823fb74906f7df189904bfc907d3073ab452275b2f38f261d1aa7afcdbc63e37c63ffd807d71de7d33972faf7d33ab7cf5b1bd65b3ffb4704243fded11c7727eddb9977744779b7e4e8d46fc089354e07419e358c754cf3e7a73313582ff97682b3450684de777a78a7c420edd3a7f39461e49a1ada7f42a0f9ef4307411a4e3148e79e4f35d8f725b2e87dfb452eaccc3b5c6ebd6fcb5db76c2f81b4b7c872b608a47df4283aded790f696b7b7c8fb1af27dfbf74d1ef10eca3688f3a63d3846bdafcb5be548e4fbd08fd807752b1279f10eea2179d4f2a89eb46731c8a49d09f2a81e698f20b2f61375ea22cbee379f6affbc6c6706cc86f8bea8066bb728a4fd739e6af10eea2ede41695f79071561acb4282f3ae88122e972e61d9ee8f25658d207bd057ad5f62df13669b9428f6a57e82d3174ef96f3411d20e9f9e77df9996d10478e0ddb48f22d39e4ade6c480132b0d178f35dae75a1a3ecc3b24e8a694f29c96c99284e7be9346bdac7c27d94514453174d0455114456726b1a980200882200882ce4c201b9ee7799d73ee799ee739337967701cc7711cc78d338cdbede014fbce6f679177d8ae61d6a775be9336d6b7d645f6f1a3d9471f8f7dfca0ece302903b30729c832eea94524a29e52d08cfade36b35722d8d9db70eabdb76836c5e3daa39ef185c2e57cb450f1d7497cbe572e90141f073cf3be71c0441107402646666e6c9cd85bc83f379adc6cd6dd93dc779c83eda3917d9073fe77d9938ca3cc027f6a9bc3ed5f8a49faa3ed20d3602ad831b5986bf3948b93f2b2065f7d7822ec2f720b9e42badbe7a97ed4ae6196fc9255f7b25cbcfbbefd3b879fb9f8335eced3f921b56d6df2cfdeeb496b4ed79df9e156c0ed6591bb6753bc8b56fb28cfa76d00bedb5ceb5e7da0c653cbff9266bc3b6cdebb8a7cedd5249f8d637f247f5a6cd020ae19cabce79ee798b3fb6eec97cae5ecec16ee71d3cd56dd0f9e61e59bb5a2fd8cd39ab77339c0ea8c4e955ea4381182471d0213353339a049000c31540303024140c87e3812ceb3a14800d8ab24c4e3e1507234994a32086611886611086010000000c200431c52cd20240f98d80fa372520bea03b32db02dfc3c5c0646f1b737b5671ab412a4bd3d0b46d8338fa132f40f8f97f0e6842a284a96cfdd1076c01395441522d3f82a6e5d29d49a69e6cfab01634d157934db4c0396e954670f0205056eb49d63bfc6974c9b3a92ab1d84b2bb7ec8f3336c46497d4e722abcfb4874887f6508536fc821b4fa23df3138578013124db10b427a358fcf9a2cac1172f72e458467633bd79682c41ed91b536a90b4b31d5ddb59f787cabb1f864e8fa938014d16c4299fcfb881cd59999483f8900b9ca283b8bbbb9b90451068fa0516172a669c0b9ff57636a0fe534bed582442000c630a942173385851b3a1f23115bd1187bd05031642f476c7ba424795252bab50139b366b1a405306c6f4ccc9719f1b67f155d434e3b09921dcfa2e435087f9f8b7118c2b44f239196a50a51e8c719c31a10b57a80a4a83dc42045824254da68bf187a0a7ddc0006a67c81f8d7b79a5171320507f60b9825d30ea3ad38a751c169c4e5be36f60cb1d66c30c0eb092870fa0951e872814f3abc3225b81f51ea6c868c0cfe5c27dc12181a59f5f1b78dca9fc2bf341c594c3c0e3b01aba9e9ab10319139370dc491baedb28c27ca48d09791302eed9c8014a33b9d86b6ee4ac165a5058ae2fded93bec92352d0696b16a8a5bf502c842c3d6f7241e152d5142029d8dc388879a6c5dca08eab615a483e87147525290c16f38434eba1662fd488b6243da065b71fa4f00c76499a503193fc16a7d8005de30c26316d0a833392e38913ebfd7084331252c47ce24864338b0960094bd64caa963c24b9c1beeabd95ecbb6121697eaf734b3391c6ac9af1f4d1731ec476f3131b16f25c8198a9af8489b787cc6cfe572ab340d2382d2e508241d27518ed748f06eef1af2d909abafae0c5b92601d650e0ea8204838611a26f1c6169273853980c39177cc37c8bc09e1dcc129303f7c9817ba7fb910736e3ac80937278ff91180543eab0068180e8dd7a3d5ca04fced799bc12b078702614291a96f1da2323c3660de1f3bbd6263617a1980d2fa29d96a4250f52632363131e6a0ea090a415967ad27738a334ccde5a94338abfbd2382a7c33d9c3d6c10f0c38b43037fbd3c18d1374ffa8645cef65febb6c2fb11069ee80c7b970a7e8ff569295292749093c1263a56f1b16e0a72eaf490e80584701f5eca3ac1ac0b356c3ae44249a42314bb8c0d91cb4cb20c86bd8e0eebd90b1da7ddf774a632f289451149c6b2dae220b2c8a4ece63fca9bfc56ae532a9ab4f1e2d5eed41abbbb3a8ddf0c0f33830ac31982157e4fb8d9d7361b2584b6c1e8a8b1c1d677cd7869aca31f2e2a72f8c39ffac0d638db85105cf4e368f14b511a7508900b6ce666c46f84846a5229e83b3205c87b759de873182ba204ebebef8e0b4a298737ba3d1af644a9b36cfd1edef8500891e781fdc9bafca08592d73de6d2e2c5573fe5764b929d817b5394b756da5276aae8a0f62e647d259123f98c6418c900d0175afccd67c6d64bd222a487440fc5a6c6ed63f3fffa346859bdbb17079ca724ee535f0a1d1af0200df269c898621df1815527a4274ec8dc3230e863158f6b9eb96525348b2e3444a74644eaa9cabcad96b0e12b7b138115bdc82ece7d4c62224a569448681384e8738495c60946fe9caed286a639a21aaed28e5ab463c9453f47b1b304e359abb66e324edfaba71ec50900ff2d4f9df249147b65bc8b12942720b846abbcfdede10186b14c2f46951a6fd37ee65d0bbd0820ddba418813d07d62cf156bf30610e0a7d6d842f3f76a35064c44b32a06227df918c00c7d08d7097baeeab8c624d80670ae83d966f7bb11a88491b95f3e87e3ef13a51e8fc352da9ef9ed75ba4446cffff645d5bbee462381938c6c8ce9dc25257b7c33860a3f79a160867317139858ea3472b5a8960fa635d10e6c0284907c2204a9d3b974e105a12dbc6d741ed924e107eb2908a1655f1e998451eb12a8b0eaf12716d96ccfb70fcdfab0f3253cf5f9e5ba446facc66777b63514acb870425899806ea596101d5340c813baf06eb94a7f42985cf290a2036c12bb4b744292fcc8d896c93420a8dbb990b04101904d8c52f909439a3f6b1bf1a397c75ce4ff4a10bd321e32f50c896e4cb5f9fe148fcd9700c7e3f38a0871935d2d3c9e048ecfffafaf2587ae45bff8a2d289dd8d47f7b06fdd5268d4a3ff289ffd04fecd8f6e8c5c9faf11d6f6fd4330fc59920fdefc30d27b85f5d73a876193a657247b1116a4d88dd0f0bcd59392629480356ab2cf17a63a8ca37ed22a32751000335a3d2dcf69cea7e0384e53e0e8c6b4c79366e36504d2ff29d7a91744bbb37aa5c9d12f9c640496a7b6cb1cfd0451cda9917d0e0b97fabcb8683f607c9d1b604eb4fecb4aa48d52b0360a22c97aada4c7096c19099171f4c07a186d381f51b4cbcba01bb996b9554e059b9e0478645793aa30a3336925a3eb1c7705e637f72ed2c613f5f8072b87e48eea9cb45e2453a168bbf559b39059b0b18098494b0515cb35c830a7cccf3c6c0e832c6b61788e0722392361a643dcaf9bc185e774cd28dacd887daa4c327592c72822724bf16ca34680c22a181f9b470231055ba0661036fea389b93a9664a76953000e59ac28b1333357f4735b530d2d7412f224b67834c5832090394841e306f96038493e147d6a24c6611e268bb8b838088c51c0c7c42f282de337ffcbca009c4be2a3af655b4db8d0898c0396a8846aa9aea682b107034fd50b32ba44fb083a12bf0cf76bae99f4d95c07ef9a60a147d43421ee19081971ae9fcbafa604b49bbe7e14e997149cb875bd5f637052c804cd1b43a4e83efa7a40ed5eb2d26436fb374a685b5c2a6e87b44a7678d66e410ca8224db1df70ce8890106a802807a6c01a86744db1ce270f35865543cc13e0f6a2ec7f2b829377b660ad860382f4711f0042802541809ef5ef222bc0ce444b424b658279930de78ca84e7b9106fdf4e6210f6f294485d570c7a1d70ad9ef721eab3410e4c801ebdb406ff20e262a4647624f95c4c651e5dc463c434d93181cbd8f0d212ff7a179344b0e62015964adb30a67cfe6cd92186507ba3057a7a71c4bb711b15c3ba44426462bb43d3b9e893af6b2a8bcfc4489d0b0054aeddb146486575b6c1b4b2a970fe4363934c0f15ecb6d9fd80211f87c575fa023b685c0a32e2cae30f679ab7ac5a2a4d938b117282a896feac5c7898bf11c3e9c2b0f6dd224d4c9df4b84395c4f16a6ab5d8900f834499202c9f8c7ef4665882f801be5c7c24f138f0233a75f8702b105a15d9a4e94bdf56973c654393939f0bcc99a588841a9f98175bf69992932d57828c18daf7ecab2ce2131f858a69fa85e9a08b7f29b2376d6b159df51798bf7c5f139214d59a03f304cc360dfd6305b810b12a5e98da0432cf78fb7170a3765400dbff9c7400560453eff4c900e827baeb3f5af08ac51024bae4a812fa35dc8e4a79541a323dff7c4be8030a69a668bafa66e961fa1f15e6c825cb13105224e745582f280bc154a7be6d0b98205848678e9a11906fe4ae9e5086021a75314836b1930ff10eec9d89aa9be8cc143d083b2292279758786ac9dfd5d3988283d82aae45fb6dd02c613ea0697c35ec5f5846db8eecb84600a20a86d947e3b57d992f00d6de082449e12fca1e6848d8c74668c266bd9ffd673baab27c7749ddd23034904642bef4590edca2d370920a194335b7767a6522b0b16f27aa1a5d8889d0a18cdfc359fd39d7153bce269b9b37c90af5f08525114706fae6e692740a344090492472cec7eae86768cd8e2406efcecdf4280e3a03349056c2ca247cb74fd07730f5f02988f4bf4a9a3562a409b7e15690c46a07e717bbc90c7631f9f0a7da046d7c4442d2e9ffcf4c6becb6977f81700bc277717ad9944768494f2e30183f4cbfad1d317f9088051facbcc162fb2bbf9b6f90f6e7b055b7801018070376b1fd09701098d03052f37dbb647badafa142e5eab0e19ea516e033274c089e05b7b1503a10e53275bd4727b8f947d7b9f6c85a18babf01375f81bf474afe58fe2fd7e15ac01e641b32b85b246bf593dbfc18436f6dc97c11ea030ed77f13d17536840a9d077a29216079042caef39c25dc35dbc5f28daaa8d31586111d9c2d642463aa2f17eb562d44600eb18632e76e35e55d40900c18bad152879a2222d28141289879a94d216e60330d39d51c6fa955699e949335d4249566ab436265e40b7863b439e1a13f6d99e4e0ad8dc77d2f29d0ad3c884ca8332ccc2873ad907064054b704227c8f424b8d237c6e049861ec6fd514d973b879a83ac4e21475d3bccb1cc567e32a0c92a50b14012a487a4504f79d1995b9a1e90ba534b34fe4586711a81fef40a4277d201ac6655b20f9c4c588bc89133f11dc2f0aa0ce71e2739b302f2ad54778134453ad8e001841306f36455dfe1a0d165fcb08502e6719f0352a9e6f9c34f10a2a84404b7b56704507cce052864c9b7dab7023c4f3d25ea03c4dc434563653857e147de5417dda63ebfc3f349eebc2e76410285b475c4e389ce740a4c69748e66a3b098158e91734ee7b2b355ad363857250ea1ce18733feeb2f35cc4a7eb091d9163d22e84b622df62bc56ae3b228d1de11c20871a16d4cd7b92d4f749cf46b9bd1ee0e15c50e370a75d40e86b86c7dce10bcb5ca261a917e68c4361b9be4c7c0b57bf9a8b88d7c9b15816ad1e99339c6ffce45489152020deda8343eac8da6d311679d90630a0e19e5a552e708050afb20cb55c18fb3c3c52fc1e5cf776dec2aa5d3677c828b07213c9aabc43774aed044abc02c3e00dd97d01177484bbc0a1c97b80b02d6cb5aaffa69a15cd5128c3f6bc958b20e2626b42a3004790085beff8a2da2439082460f41982f6dc9b3303e3e017c8e00bcaa3002d80d6f41c5b99ac32d3013e25295ac25d02e3b37b40cc95f3b9c28717020d1b8635dfe40823927e64455aff9d51e501cb995ee1132dbfd7da1a96dc17d4e19cc66a57e42ccd4de2ae266a10b9d9987b5dc281128c1e053ed6623314c81aeaf6ae72059007e68229102fbe986526d62a058faef918a58a98b37ea616dd2869151fcac13f9dce68974d25a9d1086298397b839c5e08b30ceb36a4866a278ff817b60644c4a11c77f92572e3886e553a387447fa019c066c061494f33a104882db20c5f2b141c1a73229675b2dfcfec9adc9b98e5dfb4884b72f5b0a4c1059c47e6951518b9b85c51b06c62f30899c9cf1d8f80e7009188651d6440866bbc4c1bc7ee8c2143200eb712e8344b06740459b9fa26a8a033dbcc618a1d906a6a31749efc012ebcf63ab44e2e6789b25ce39a98701c1f3b935127ffac6f3b284a92f3c045e86191415ba0eeec784f5d4791cdf00592b775c387b0bf40136d88bd503c525e524f204a62f80d947ec2e1680363952d676c4916064cfcc2511b83270242c465447176770007b5cd7aa11a5c3e60a86e9eb1b0bd852832e84d45b5dacdb4db25b26f24b0ff059f486947c3173495aac70212d857fe5b6185d432be8902b51ad684749a43a51eee3ef8b51e94c27b8270928e3e0b19441e07644e0745139450f5337c8456b285d6493a0c12449554b83bc2407741f3e4719ad1b90104fbc59a24ed7259602e2cb1ca2848007ff295f48b803cbc68cfa36f86993c0a612360f2d7400c9ba93a62c8510e7bd0d28e8271c28a397c8f9f19206430e01b4ae4d088809a4991f560517e29c1a6c511f9e6e66f5e0a2bd566bfccb218f3a10a6d506afa99e5b1d077f427f8e6eb6c13e1af4e69836b66c4db9707227386f3a20076562385eda75969a717893b62b83a17f4067bdd7b9b14cca728f0d4683a3ce85cc03c48613c97e2ecb7486f35d179567fc65778b7030b4083d906c3aad875f7e91b5ecb475373e4362a5399248dcfd4002e41ccdc881210b7adc8e149475f0266ba799902298f32739301b76368ad94398783a0d444019799e7d214300e0833b0bdacc0b4acae510f36ee89c06a5c000b3a1b495daa3ecfd8ad1bc3b4dfe8355f54b942b5b935076ca49e405511142812e54f0162522ff2c8e86a8ad6a28326442fe2651a457075d77bfae2c1d9eb382cab346499bc929bb27da6767d19a4f93ec279b339298f87cbc31a4ee256d9f99a97a35ad9be6e2da34e5ae26493f68a12cd96351c995cb20ab06ea6e8acf14f76234f86bcc5eb5d244775f31686533986d7a34280735c72646cf2139831505c392a1155d7ad63861226978241714cf875d98c1b71af188cc5a5679f164522d2d4b0dfdc7cbb573709b0deccd0cea01ea4e6d63a2e0cbb5143037f089b28306334ca18e9995e9ee94574a477ff245a918f6c1846a676dfb80f3b800f334b9edbe5cd63be1482c15894bd109c7195602b7ccd45de9c58efc97ff7be17726f049afec455b888ee2189fd641a801a90925f14484ccc4d28202515de24edf177c6c1de88b0e8aea25c749a95700ae098c0313427214a0816d2b661a68248555a41dbe69602b5a9dd43135bd018f74ff162590bf075c5ece05841fa7a5a84d709663833a7152403c88e9723e70692fe9a1f0175ab1f232148bbc88e766b82e22c6747e793ddd19d5f72dd1414480d3a7202f1106c7358fcc17efec17380c62964847c951987d64703e14f9f48271ed47f1457a30f0d53ed01be5ca322b33253672ac47dad415d028748cc3013e2699c928f4f664e17ddd40c2293929ffa786268b81e223cb07921c7ae9549bb1e53e8aedcab387b32c9a7f11889d4383ff4d9ce40989d960a1d20e749f41e178b133e483bd80ed4478aa0be033d55d61826a09bbeb1f10e045e8380c4c6400c04bcd870b8a8f39145376721c824b333dc3527e3610b7a8c481344b21d6b8f0ac8b735b69df22e3a7e9455689c304221570c6d133705f9368e7d01353033464420ba862323521942cc23f1a68cd322c6c9f544ea4cd189af0dc719285367e7ab8359d55959336b914df7ca26c7e676cef524586b5a01ae9baf34589e7c2b581ad42cc622588e996d0e809b299171c9452c91a69fc65d611c56baecfad5b239e18b5935b7fd9f575a2371c72bf1916533f01203148d22d8a08a5fe1a7fc186143e4a5424799858353231a9826a395079b28e75e7ccab9e9b8bdaf4263e7b2dfc89cdd827c7cf3efec50f86b5eb76094ff4268d9faf49e1db5075b10727148f024ac9af5326525b2d8f26de68c22c448ccee7db2faafba9653ecf7c65862650df08a6ed1ad2e231caec1562006463b72a60543b10942e6a8176f24ba6815748ef0812c8482b50a54410011c88e52f9c60eb05a1ea9444ee5747038fb13f2eff53d27ebbbe8077b1b18736b00d43e90c26efccc0e7e538350ca3851f210f3cb8086cc68cd9038f9695e1e4b3f1c4d3d1e884e47d41b77034125352f118470e1e7d53a9748a934d03e8b4477198195c53328ac33f0c210621991222115b9696a2c81b10f55f313856a0386b86a003acdd17b41ec8523fa3614536124b5acf819bd75eb5213704dda829bc85d74a65b172bca009da1b5ec54f514a9d30d0c0ece781a483fe752f8fd2ade5a42c84e4c0a1300e69441a3c0850b5223dad0211e14132f9d2da5e16b46e55c8ecf46054060fd40725d16b145d37c0f2cd5a1765217c5ff1023241cb30ca03302c9ca45082f098815dc044bf1f516cecd0a07379554562ad950cf26f799840377d603e6f3482eb83d13d21061a4bb419a0b22a2ad52124aeb7b1f7e42ab326163eb697a93509a820e83eb144c9be8c735138020195047101221bc7bac483b5c99a6f196f34aeb91c809d0f1360cac89f850fd73bcb23d3e9c2634bb4b8f7aa836b29215f08f601f4a834822726a47498e6459a431b2b31ba8e204da5137be156c666c94fab8720329256292c7d93ef4365a7f258aeecd2116ad226b962a9e8edccbeafebfd07f9782e80e85d0141c5e8b2d3fbd20d2516519fd53126ef042003ecbf7b8394bc9f43117c911154abd5faacd33b3ed31d47c5d8bb8dfd3d323e8b8be55901c9dabcb29a2d2b3e7787136ca22b8c3253b9d885543adacc0a9181c9d421d90756fb5e8afde4bc27741a71425a51a79bf5207664690feafac9dc066f990a8055760e0aaa0559120b0a053e050a016f69dc4c28ed0a3d55f5078d96b136d4f823c1c0f74093c742800cbfac1c614f82cb9f9e04eadeb3feaccca46000cd5291e24918411a0c9d8231d4f61c5e7beeca0674b722c934d53236f480fae3f3415ada4677e5b6e97df42f412d25fd2c1748c8c487c9509a6243ca1ce2a5b2622891a930f06ff221462e7209e6c7069504d4107695fdccb1801627219ed3ee23c3dbb42770543e6df2bccb2ee25c56ea4e65c632642f6ed0a729f4eb618e6160a7968c760b77f41050505cf29739ae30941e25667dd1bc7f28ac5201414cbd55948f1a38b6193cb491e6c2cef62c226026803d417e7bc5b20311d653ab18d0e98260c88f7df7aa10ea3f9a1e4a0761acfb6c96f24ac1c04f935c0f28b2457232c958dc766eb16e0b2b09d9e230fa769d49997071e4de0362f9bf9caeb58497cfbce2d8ff694e6f6f665aee96a30c13f882016f0e4e07dcb78d3d00423230972b3d21f1c2c55c682bba906ffea62f6657eda4dc3a28e542b091333b24dad6c0ff63a9b994d663bb7d4d28b29b3053e47a182e12e9a443d73839966d924ef3e8966d7d3ea4621f361e517b26aa8f110d6465724fa1b89955525950c2e668f1a4b30ea053578381ca8a19c5ce62c2da349f0aa900a335380a96c215e43a636c2600f58b89538887fa6f57f4647dc4dcbff5caa8fd6471ff492cfd41376b1821a9d468a5a2d40143b428e0f2821448c8e18d601e3abf3c3f3a722a8eac8cbc3ab9d88000b8efc250de3c7d3e785637230269cd67aaa95d9a0cf3ba7e8743cc89cd630b59e6d8b5bc88714e6c6f656d016dc1beaf8ce5434028002398148bb403faba14017507c71361cdd39517a16693e9c8cde19216c4ec87e46477328d4f52365e8abd882715dc7f960dbb9d241c61298b0782521c9de6c8d66d6c8fe6adb79dc04729e8b14cc6178e05ad90bc9f879952664fd1ca2a1adabbe51e856c7d1be40cb6a1cde605237a188ac6699ffb09686dc82a8991148e1688e99f8dbd38d85b40dc86b333039b80792c0a6001edb188c156e7f7b0d2c13d8447fe5bd97786100ebd1b24f87992014dbfdcc78875d745560b043cb096da79f33d3bd40ce29a6eda0c39464c2b823118aaca132f9d9100ef9c991119c2e4d1636948f005f25d878478f0f317387f37bdfa3b1c7b74e51951cfcc1896c112786ee4f965d2b2491bd611c49a36044cafa5666bab4d7c4ac4b0cdc3f835877437ca65f552fea781c567d4c9d2c0a1afb827f50d3a9bd6620e1dc285a9d5a2417ce7adb52669cec27a421d6f4361c4709427208a4cb0a634f7a08cd61ae4de26f5457966fb48ed84d3d938eb025c5e3bb8d1d445f0c75a9aee019a4189576ca408873edf34a301f053162764e17151d10b0e54659094a3bea2a93502da86f0a17d8181e35b4b9fec02942e47eb17b74a5428b3fd8eca8645524a8a48c497048d3dfd679d9a99e2267f10a2868ef000e7f85bd3869a72280040c237bbdacd3c488c6c038a93ad9f0583c60b68e6b4446670b2284231c11842d0aadb929d1431a0febb051ec33a4cafe22840c826295c0f542eac265a8689daca47329a983d0de1f669ff9787fdc099937937233a8412be838f77807b26bfa2b949ef29343c0c7b5afa9be4149221764fcc1a52b201293934c8e69b787385a3a98c364e7afd06c72c594889763a0f84d3e83a9ea1cb9fb06220ef1b2ab4c16ae5c7909663f39ff0c470a404db927152c05889543cc7005bb6f8ff70d4bed81b5147f1fbb4f2700bc6fbadc80148f4aee289ee8df240203b25c7cccbe317c238ce6e8f77a4e44b4d7f2b625ac823ceeb2d553d9c2323a28531fa7b70cd526c0b1504c0145ea0efee2a70bf8ce46aa1584f7003824a7adbeb746f544a1298b2302e1bba4e07a483de1714a32ad81fbf05500a70c6aefcd662b6791b18d0734ddbdb64f283a4ff409fdfa47374aaafafb3465289332e20f74c948e707290159d40588722ce4fe61a988062734e47a4f1e086920c2c4bdf157cc206a670f1cbae26a578801da111322a64468708973e3780d668dd90588add099c4840ac5d54afb40e047f3bb9bd52e795327a7a0a7e98583df8d80f50240d15a76aba88e7ee830c30014aaabd2b0981a8c50de13c4583d0e39451d516be6310ba87741789434c189e32b724924e669a69b2cac9a228a385470bdf25abcf379cf5a94fa84fa0bba2efa3a384c0bc304800e8636cb8b6d24abd890af55b8fb4d4499b89544b22336bcb04c4820fdc5dbc8a428770bee3936b1478e36cf55607d44f59688fba01b995e54a24d8bd7f875f0fd8a98df01795e115c991005dc8e2261113687d610b445a596da35745c68b7dc150548ec81a9921950590538602dd85a5dddb4001d1c2e37ea4baf625868f8e6d8ac9119e5484a313cdb8eeb6530557775e00c09272135b993930f412ed0635ee4e65924f293d8e7f99017ec37cb41e5438d2122d2d4e43c7b4e289aba261e82d73e6a807dc5ea6e01b14ed9f7a3cf83c4abbb9e53dd12b1a9ee11642da7747cb71caccea03c4820235cf99498540e25e5f12610799d31e9b0f17d01d65eb10b24aea2d8e71d9809a54140d98041b4b8f1bcbcd082141f53541902e7ea5a1d32e967a8e711caaf61c1cd2cb5d4604532e2c69c7d40047afd6c5c69df593595a3236eaec2b481f86e0dcbd99e857a9dc24c99262597e76363947493d941403b2cefeb117c6d79fe6c663fa93bcf154335c28f6e65cb7c802c502fb69640f9a580aa01d31c446d9581d09d2cec45f596596c333bb24999e266833338bbbd621bb28bd5378ac4742e2e8634d2f99269c5012e3420deeab6c98fd6a725c3d44320bd46b3d84b11e234a41f1f6f4bd5d38b2cafc89c1e4710080a89a6e14d93b1809d9d216dfc4a83b29512fcf20a8c11b1197c49b42c4e3040baa8d5faca87dec1ecea146542fa79b08d976c6359d2e530253ce3442367f18e12e2d7b00ce1581528b29f87ca85b006c675192ed1d5d7785f723a939ef911a1c4c2fb2c2aa09abaaa8fb52fd3880616566e85842153da86ed48f0a36259a7d2cd4315a05dbc2217438ddd8c069f42c3e6356a2b8b0718bbfce63067b534baf27250e4f7cef252430d02adaf35c1962edb8116aab0c918e36cefe05374982753ffb80d0f867fe2ddae359062d234836b5332156049fe0e2ce210d65c7d0679d3455febf877908058d75a33bc34d1155e5614b002b16035be28fd492e284ff9b2543a95252fbfbf3405263dcca5495f3f310d92bab89023f95705c100587c35b222992ae7002d8d68e1e33e9fd1ad7ad0fa7fad9b0004c76e7d0f08311138b1b319b385c9693c9697042e82764946185b668cb5122acc0d826e9f6f8b4685becc55c531a38222114199f4f77946855b98181976685a4cd82627ceb4110366edc9343d1094972f09582a6766a05010abcb193d2deb2d37af155d28eb8032e8b086193136b0804621581e681371ca80db7165710bc4a032a1841b52ac852454a9d9b1609066daa851f34c766cf1c5a0c15fd844d9b8e2944fa64e4b1b0ce07785c4eddc14880e5bbecaa66a0fe1da81cf8894dc0166fb6ddc14125a34d9306d808829391871ac4919142ee48c40e8f3bccd5a06d746c0140ae26291d9ee330557f7c5c5421cdbcb497bf27a5006fd147f63eff544707d1e599a6d3cdccb52b13e7b3d654153b0a69c04831ba602224785f67f61153dc60b9220712f59f97a386e378010ac69d032baa2894957c9916c63c9aaeaa24175fb76a67a830c442b864e1991730f114241641eb297f133cf9e290421556db3e97fd04757f88904e27f4c3215523fbdc04a2c49be1545d3ee52eee72c6d2f4a3110938c5b4d0d277cb8bb564159100ea1f1c9a7ee5463cfcf3f3ce963ace93f6fa662b9017f00e4a868f8352a99681e38b04115bd01def38c7f6e07e1fdffbd6a8d22a065371b395f00ba98f954f74443441d049dbd9dea1083100ec143e63ab637a5339c23ad43f820653f567d981afb92f4fb0c3c3ebc3181fcf16eedaa7ff8bcbbc1480acda5520dac104d03dd4b1af0c97210e31168286a591aa889f09966329718f0d36963c71f9f95173281d69f94df3462cc6b75b42b9fcf85aee96164016e5d14dd4c2f300700b7ac4bdcde6a65f989faaf80087d6d355bf466b2db139001aecfd1e333006cbb47066fb1b65d2ed54026f71d0aadeeff602d89a0ec7200dc0658be932de10f78b719366b3d4ea514dfad1b145fc28fae03df2e919b4c311c04b1aa5fa8df46979bdc13f8ef5944b5f0841410a603e00c04a570e0787b9534037232078765c2aaff246d8e00d879e90f44bd790675ca399f0be2d076c27e607dfde8b5c1036f9b5eb4d475566ec6a06772988f9c7cdb294ccafab8b68d1029ef4b4f943e632b9ca6cacb1d78de13ec3f016fba9a400d507ad084ebe518a9ccf70768417e9f08a295f419bc3326c75f86fd34b357caa3a23423e01bf5cf55093051e58aa3f17d7f48606e0c77d0da972b0245467065ed236edd11d70c20d84e357db1cc50fe37484826580929a869738a6e612e4318230741450c96c2182ea25c14fdd01ba03f14335dbe9688001ad334bc9d97fb2d56971d7a9e14f8ff2fe453933d0f67f34ee0da2e39e39df3cb35bbe24ed372c9d9faf2b7f6f8b7cee4be22e3a2c8d91a144b0bd60cee21712bc92792a02f315bae3202def9804f6c406701d7605c004a3bc1eca316367e4983f8d5149f93357985dbea27ead810ea4e95e95a63b65e22001fbdd713ef207dbcafceb5603c6ac59026f414a5db7150036b52067c4a865a372d325e2883e4187886d793e68345bb250360eb3944b38e17caa60871f72b4e8e88803f7f4dfa8c0267bf15ad50c47e1810b57488ed5a5fb12cc28870f4ef9267efbd4157be6b14be162632c6987933554ed791c86426b0cd8eb5b9b6d368ed715f13ad858d40ea35cd5262d3901a4d83f2584bd5601f1e77b2f8996d17eb36061f7bcdb810dc7245d5673d1b56078ca7c2238f6bc5f7ce097b7c481a87be064e5ac57144dbf9c21c580340ddff2894de13aaefe32d9cf396cf0634bf382d20fa6695d154c7197c9b8ec7506e247d98d3df1e99fe02ab492b34d32a26bac2ce32c90c234ad18221e587a05ae23e5359ac0284039a1bc0cb1d8f645a75b464cb545c2d748940a6885b9c672b35a4641e340d4488201dfd4e127993de473e4dcc83c159fe57fee01b80f18c36bb4db41c4797f5abc90273d967800de7cb769e535edb4e7946fe5a41a483a51baea65d3c3b70e24d5c3940a809f239c88097dac2997a6e87dfab5f27d7f24901876aa2472ba3d69e480489f91f9f34bad28985cd01fe617d6c7827a13dc488adc3e89bac0df29abb54389fd666868b784e5cc25079c0e20f6548aaede97e0125f512a6ea302a581571664aa91fd86ff9de97f27bd78730deee2af36d4666c07268490cc49c62b6e3ce5eaa256f08b39aa10060f86e8204d9287480e4b621732055d762bfd7f6b227a658a76b1374564be16a35a70ee2b2a7a087b241e4cc1b2de8fe301a902befe769b6b85ad03fa1437a7127f2eb15668648315f18d6edf703d65d94b5de4709ee70c54c30f5d64fb692324427fa37e2884b40457c3d9a9ea070dd32d642ef98a95bd2d7103c361ddaac2366768c2a4588885ca84ef49ed30a620bd5b0fec9e50a8d9e98223425604a6173a597a0e0c3520c6695981c0b3830224d92fea529e93106ff9f5fc3852cfc2ed6a41a1a8fb5da023e05ade2af9b9bc498ec31638be2b226e3aa893785508dd3304f9974b92c31da26a78604ab35472c9056f437ed387748c23196e9ff1193b0979efe9130d33344160ed5621f20509c359ad0517ad96883f8faebbfc795d2d062783819a3496411bc5ef86b059accc2eb5e034da126a17aebd366d28938cedf084795b3372e3c9bae5ae0a1164f8494a409b81741026647f8c37a54ff96c342183a89a31dcb4fb9951154735d3d0a3b3611192160bf5ce69ad7b09f4eac59573baded954a3d5582dcc52346d30036c5d930a8f0a09b68cca937e181e44df5bda0e10d9908948b365b970ade749336c0c0f4f1c269c5f93b61b27e5cf22de3310095f1c190a2084e1471b60a22ae41e80c5c2ba892a076deab3bd75a45d04593865604cb6a64ac15635e35de8bd048edb6f0d38554996ca2146f663d42afe548c2393070b04c5e8e0009fda196379f0ddce8e392026f5d116dd889cb9058313a532d82d117a85a86fed278de34ad703c5d633010c2a8e18f2474c7d4eee97994096c1e0108520110d0e06a208bb224f83e88d46185096b3e0bbfd44b74526acddb4fd824b4a4d49cfe5b73817b1ef9e33200eb09cc3dcc61028528b63451c01e078167ab04f50dbf70e8f68e1629460e4edab7b7e037f0691cbadc5b9c36655bf0328f992cc0cddced284c1faa3e1dfef43a9d91dbae9dc3a64486aff7434c39ca5e6b8051d454a9806c6c23790748a0f1eea5e7f37ac3044ae3c21ece803ae3ebdcb964fbd27437f463469659b2e8c2aeb8b1c70e3a7faff68c7956abaace1f3bb6e81184afd2fe354327d20d818ddf78f4c7c81747ce2343afe07535e65ba9747b6b223bdc7d362c06aa75f962595041d408f5e5a65474db4cb7a755d349a1917989dd1949fb80630b14236bbd60a313ac405f09ad227936d8be98be57be68d73fdef061faa7848795151594f0d040571ff44776bc9d79dda81a939185e67195f67f984c50258b8a70add15d6e8ae310b1ce0407db687fd739c9e5fc90115044195f23f8cab77ef195fe3a49b6862b4c590de31d034526481795c73f02c3568002ea85d445fb6b1e4dcb7321087113348c84f384b34df16a7f237e17cf8c543f463e9239f3bab68d04590bf17f0f58ee9434c1c29ae782ee3e5dc6f0798773ce79b82ff15124356fedc7f96edb9801eb4e4041a2021e312073b5318744c1a1c6b14e86e2cf99ae5de89a291e5b5293b484068ef72cae436abb4c0b6d03615690d43b08581bffc74019fa8e3444fde35f91045daea9fc478468583b6d571ad30ddfc2304cb15bc04c13a46803bd805e660316d5300e3498e7c1f4196761aa731011d24d9895dc2927e34adc7237ee42463585274bbd5dd711e3a968798d2ca294f7b48c81ef16878c0854ad9bc449d81fcbe961110a57e99eef1858a5e8d9f62d5fe5d2284d9da4aaa637daf150215b06736ab2c5f0723d0f68e79ec41521a588d670c2ad0dd0349c9b64c5496c466517e077f52a8c8afa0a59e551efb253fcd3e554fa1284166098b06529aa97ec918ec4e479388e5e18dc864850e5bc05842252bad4ad93edca857ae6ac6ec5e1c658a488c1b06d692062ec9a63ce8f87e2ed2d1d493ecb3fe8993c42284f5311674219235972ecdaeb1efc74feed50d3086f38719aab3991c3c6cf5211741519ab863f6b4d5f4299e7e832a52b30962c677baeccd79cc823cd155146d08eb699d7e6bf7a089cd5a1eae84708144c64578d20102b1010d7f4a42ac0a21404b02243a01965efe8371569cf7f59c1bc894a2b59f7a30240dfa8157f02e3af0effee7b2b03097f7e0a3689abc8b5a3414c921155d29ebeb873693092e0de1f15ea5f123ea24dedd0d159041f65db5867bee85d56f9248ff4b0f57a5caab5461fbe5ecc1b9b524413685db363589780e7daf644d28ee37f906f38fc4336b4ec0a1b7ac4030f6ab9ae35ba7cda6c672dc764e3edbb60e9d1be58bc870f02bc4c159dd16cb5eb713bd132de1f23095553e66f028e23b0ab0d6fd0193fd3490b0e402287e32419e7cb668d689d25d1bc3c6d0dd9bdcd841c087d8338a7f4b12aaf06c61e799808b5a80f79777b65e7b93b0ef3b66d2a6aef856777ab3c020cc8dae28b6aa6020ed4de4cefad58646e4ac0039b6f0343469ddcd1681462cdc593000ed0867d67a46cc718c773a4f652bc4cfe09d6759737ea995235285b5e7723ba9dc52ca15faa63cd342999dc060f6323001778535acab5afa40562dfa4894a71bd5ffd982136e9844c7955ce9792926896b4344941556a26edc827ec14519845e723ab0a3717efd5c7df22915a050f4959bd4923116b6f13c9ce68f87028926d5fe03a4376b690e63d00634b9b380ee79b2c7ac66a063a4f7fc086be5e874c61e0ba692cf0b1e16302ba1ae2bee104630a9c43c4207b7e99484cb4dec0915783306f9a08049d41cb2eb2ba61ece541598f02df88062c75bfc9064c4da8f3c87cbe707324349625404a352275daecfc314927fa5cede5d018b18e75dd5cabe6041c6874a8c48cffd838d4302ae0fd29be986bffc7b9f450bfbcc1e64134a13dd75001d42062870067017fb05469696e7071e45d1628fbd024e867f53c1a5910fd0bd8c95ea23ae11c2f81626bfce1fcb3070024762417b450aab1f82764f8ac01ca74e552882862a3f897b0b1d3c7a44a00a7b48e73c4e9aa35a9f5b39ed7600b76b5a7f8c57c742ef7712d3bfae7c0377ae182ecd6e8d4e5de5672fde51d42413d1bfbd573f1344a213bdc896f70c8c84709b70e291a7c38b4bb23c49751d7b429d33b0e164982313aa64d236344af68476629c4d127b071a44844e1ec6dd6a0a1bf91ae437db1ed61deeba9922ea642827d0e32321d3163ca075d782bc311028620cdb5de10b8e4d45e1b62abdbe853ad875c157f34c8b67bf48e2e651ef3284a7e6bb356b72849a744d7869033106c2e735d3d3508403bd52c010c16a41b579d0b822e808e0c583f4f4ae0452a78686eccafd5664b524ca77548f28ea33cde7006ff27ef0db2840ecb521fd2198668e6885f4c5719846a7acd9803ae99246e15e4e79cd8db39a1b11b5322c92ea6cc49dfa2f2b1674a0f35ac9f5e63b8a0452cbf80041406cc969dbdd3985daa9cf92ecb216ce5a81d9ef0cba09e673134d1e33e8c5267b35e1358ce2e49696f40af33f4b5346096bdd3c50c9123431dd1339df1771465ce71ab145e6cd0d4486549e53330d74a4651c0869382df9951359a6be34672d310d29c88a6508f11e073980935199953a71160af7e2a5c751848ebf8124e3f623f59dade4f1b947b06965c7f4d6b79b61813fcde5e8a27569f8397c3fbdf70ed4b576486b44e9e4a1a385f4e0593888c4b1afe3a03d778e2a7b4bdf70857dc55c6f03504aa56569ac66f9a9f8049eef5db0354c167423ec69359ebcc80f2df28420a17a6690be3cb9fc58a99501b4d043746a29a8eecfd1c9276d2a53eea1a8468715697b081f2c8eb51c66193aa7f81739e89be6be28c45f092000c912bc22918440a6ef3e798b74075d18ccf2b9dbc055a4498012801edfbc09ffbe69baf4d0416f247685d0a3f94766dd48cf45653559e7c7e5a261af00146e4bfc7143735e534fcb52d0ad1bec84206643fa5f3fe6028cade8f5c8cfabcc46faf22fa574ee86737144a5cbe668cf585375438e6c32d0b1ea19643c12da7423e895fe7bae83c5d10ef0b21c06493eb8931850a4343a9e504b709e2472b39ae4f9e371d20b44b38b073fe8dad5c9d310da7424d7e8be2eab5db1d6ab85cc8dfdf1741200e54c67179fea4075d9a9c7a4f8455069f059bb39d020c22432da124a9b823852165e79cacb30f6473e8afcf8be954e5c1a2894a2c68cb9ba8f5458cae4d17bc89502296aade703cb8493029f15c27ca849215155e7e5921ecd46ac42052507e99af3e0e2f8e4747b98f3339cbbff1f0c457b151c3103456357ecf43c97a0ad8883387ad6d550fe7be8588096dbc7482d49df3c71a68d4feb604ba1038e7be6d37efa4cb9736f2c7b2e84acf9c90bae2f6b490ccd7f682ad6cbd19586205235d03f7545d2b36a04e84c9d61c835ac5a58aea009809b46481f1c79c79a4583be8c0df45da1900dc60d5686bc0f1e55d053ab1ed27b5f036933eb20e24bbb6e9d580eb3f1920e62d26bfba1b8141788b6606e5232ed1f1d356b10965e5714a1840894ecd0748eea628c33608b7a611769dd776143aaa8641f6a5196a7c031eb1fa88fd71f81fe2c79393cef967c5d391d6bb4e0bbaa4574b1b0803ab045256bd37a1d55a82401560b221eca45fc1b32d073cc941c13af82170bb64210d45c1d824a9b10b42304a1bf411e113d9631537283fa843acdec8d9fe1b4947e9681ef5e8af04551824a544eb097c5727b11e0a88e3466aac3df86ce73d65b65dba8689b9f9082b343dc7290b96806a059477fc88c7ac81ba5fa155b91a14a81b7c177812810d1bc075e79b38cd0de2b3ed5508811effd043ac73479e1c7bb43d47270de84d0fefe0b6ee706a219b588404901cde4aa35280180834a662292ea4e635147568b88260c98ee610c5d54797b9375d4c8b5677e0d0b5e4bc4517a1d43b242b8c87352b78cef161aaba0c8622b08323398db9eb18c5357d969321ea04cb6e90636b94b1bab3e76f5b77370708b8748cb291f30408c91ef13424894e1f2e76c3fe4c3a152ef732f082e17d1c1f7ff118299d491038e8ea66f87e00f87eb443050eb8dd41b05adba3ce23757a8a9216fe03598b3d14320b7ee49a978334ccaaa0b46501b5d53c7a30a305243ab6d4c5da81e41ff554dc8620ca0e1de522c4b8895e272effde43973649b097b1cdb206eb57a7bb976344d13a9c5413cb83fc99299770265c988d5787b93603652d303723477363853bee3ebf3cb69a5b9b2ccbd466b5c8207d0b072d75ec6291a8b4fe59e3c138bd1f18614d46f1d73a0055c5b914c643ba73bcbde0a89e324b989050e3c20102cfae9b0172f9cfc3d159847ee34ae7e19f69200ce3ce0fb376c172e49a5cd4000a06ee4ce868c84a76d538946a0812671f518e6b32c49a05b1a38518c63b1ec69a82cad14298e39d0c11ad41ee6c428871cf86b1c66072b011e471c786496b90b99a08631cc6a15c3b98397046961a0bc344ba893c2ab63adad5e0a180d401a1139d6b3dcd81ea8b7a0e977360ab17879d735560476806e28f05d776fd9a53c8050ba91a64d865f5fa41c90a41f903d9b4027cef16d21e414674b0f08510c8697a2f69d20cc18c5c438c5587e1dcacbdbc952242d0021cdfb35b29a0e9ce97a18d1cd927d3f8ffbc406141e802c5196f06d1b10c07be45448a90e79d2ec7d0b2960687133bc97be6938ea56217946b898ab16964a99bdf69f06faf636551ba557f654bfa1192222124cc7b66922b609bd44b3380c72c983a55508ae853a82c5da6a6aa389f771e1752abef4a7e83e9292d19af471e99bd6fb1dadceeeb1e374acf57737821423c60e9f9bf881f7063178d353848c7a8ba2f6ae096ddc17e50cbccf9c8308f8e0f967627b2c013a4d687d4ae7dc192f4f2b0245121067c67b43f41d2f21dbaeeb4c74fb11f52534c5295493ee17e58f64595b66b4e1ad3ed464b540333ec33559f721682d1080dca7729ccecbf3c6c089c161ad5b793057f1863b28f407308f433b210c57740f3e430c63442a1a4fad210b29f9bfc83d9174c84649041227cd644f2652e1c1236e88573567fba269700edac9a452c4babd3b494099f23f18413de25ab1f468d06e75ca2eed4bf0236be6c6e4d6f04b1e714b34adda427dad5d642d1ae5c48edfbf4888fa8bc0f87210892d8adca010b5e0aa1e1750dd34806303ac394f1430a9f4d143ddcf346a7b13bfa5f52361c82fe1a7ed52dc77849b5ad860bc547c1fa07424dba848608fe9f147f1ecaefd759cc1abebd424cee6b98330ed10ccb87166384cd03ff060e654ef4ea7fcf6df6a6566e13642e5939b8aa1a8c4e8c53fec2231feffa0c2e7c6a6ba9084acfdffbe862b982ef25365d1517b03e9a468409128e4aedddc6a51790137b793df1b6fbcbd27208f82dd4690be57e6062b1772a61f502ccd572e0eaa7bca992c31f758de65acd63e78b79619dcd8f5a840723a019958b7828fdad9c84b33a12ba7fd7ad2ba861fab98cdf07f20d8104acb9bc75191d895b34c9b4c1f5cf9c56bfb0d7816fde5e33135aa89e445e5ff9c2b30eaf015fa0e1419886580db51e490a0f9b50423f826b1e032d3d83d2fd0f8ffeedd50650f8bb71ebee0521b6a8259c60681e2f6c32e1c92be19dfe8e8779d538e7dc11560fd8fccc2056400ea343c412b61c592cd9641c521d1016d208b7bf421af809066905c057f5de0762720e601acb3f8373376f17a329c78f029c682cf14dd75d580824d06bd765082509a02456b2cf47a289bb4add18aa608171e69fa1da29eee2c42d046bceb592fb9ea002f4bd4ca6d5c618ab1c502939d42eb9f3ef618be2f69858adf17b080e9907e129f461904a3c9eb2494d47d9e90938194420710b2ddb1b48be0181ed5b2d099a5bba744609ceed9939ff39eff6a14c486c4ccc951a7154071d80792d24b9ae4a9da9cfb9759a4daaa064b6919308ce224937e375f9eef544e9796a621604ec487042c7197088cdd3e7f5b7f971140709c3b7e3f60114a16cbf3688d617992b688b9fd5d87e795d843f875d2f567b454f27b212473d8ef218808b57dca7c6a01d9286cd7ad54f52888b411bb5809505e4c3443dc573d35ad2c23c9f783ad6fa2ceea787a9d5c187bff7b6b590ac56ede50dcdc93ed982fdc516cf7c03d84d7d199fc366810a9bce0ae67ca8c9c81a4f8d4d3a65a5a946adda1a1aab0e10fe5f5b03612d3018a2a25c406cbf8c2465de332d816dcff18003c93a668fcb714c02591986f7b33d8838a7181469702d456cf5891403cefe0df4876c0dfe86bbd866f8028e9a2a094cdbaee704fc7547f0823299ccc5258c1771c02fe9a0e6d7ff4ba58f0d9b8d851b2441d0e3c089115e3c0b65c7a367a3e9afa9611d17714481ab5a56260318447672e5ce06ba58afc44c628c99d747cc21a590794de69b69268448ab9d38d5125d95db75af62286c922e014c22280daf6ae7f34b9976539a74e0fca294501703f21dd5d3cd18382d8a30cff5953d99b80ad050a5d2f1daf61b9c178e374d1940fd2e9f3ac0714156b970dae85d295a5eaff81aa80f5fa35f14f875d79f43bd9ef3f1ab389688800070997f830ebe04240a055c17207bad9564f587a814909fbd7d80c5f31ba5ca154f55b2913ae05d74a5e3d6a2444004d183d5af56f6b7b25d41a713ffb4971d21284481f492410a60c9f39969b8f93e6090c189700c4a1a50dc84fd1a08e81027214ade42750e7ed32771667ba3ac9ff1ae8458dd1a9296eb21f78cbe6ce3cac8c10e32173f0c0251b9484c8e89668e24976d6364c182b54aa6c16809ad6b4c9e33b6873802453a39c617e4da1a71e698a1906b26c3d1016b20f3655c6bab5f1c80414a327ffd5712facb0f3c75fc5085f66225536038307f8c2c4252fb01870fc78d2200ed2e3a3dfe9e9b041625c0b71adc9b20126951099399e1a18969138f064fe2564a483c5ef2b4a1208b71ee0e46f01d4789438a4fcce813fc04ec596341ef64cb9d2e4071c0e66ff2a2bde1132ed62021b51d94fb6f241bd35b065f133c5f83b7f2cbaaa612cbffcb60511e685ef3783292f3ce5a1c11518929532631132b0d1a29e7e49a8fe1eb30c493f25ef05c096959d54d4a73a63827f8ad7ff1ba5e0764d28ca4ab003645fa5b07a53e76227bb517a8002cbf8e50dc353ee25ed126617336115cb723baba81e9242006e598ddc957504a35f6f0a948ad9c3b64e9c0b8380dae0f82d51f73c8af822f4e4031590addf40a6cd8017a82d95359508a2cd51cd4c6c9278e218716747258403bdf9f9f64a31ed52b00b33eaa07f6c2fa89d453050f63e21c8d2eea320395064100f0180d0000000008861c74686bd4bd3f6fa5042a51ac5bc98d33de31763711112152e4962277b405af05aa04371ec6536739cbb29605aec753b50cc920a5594b085317b141d4f50105db1423375e105d89112d71a2612e8b16109d0bb1caafe4945a6badb7b24f2473f6debb8630fb44921caf6e00d13ce7546698932a4bb24a6badf91a06380c908a52024b95ae29503008c285cb891b5bbeaa22c0c853843a8b37042b914b31a32893b160752c5d6a071354e4988c15099a25e15c62b40885c05a52050b0b39250df7451eb7109a31012cba34e90032212d9100929525258ab48cb6709923af0ba5cd44ef0a23aac6a6c690c6a608d140595c8c1447aa2c21eb02e1e89a0535c8429c736e42987d22e73bfa4899a37b927912923c75ab3d95d7eb11c49ccbe82871cb5ebfbb9286acb278a51d718b92b1820bd92f68068249e80cbd1898170c778e5a8b536baca277d86e71561de044baa57463489d19e1e2a16b868727aca7235b9eb850445d2318e29113dfc18d95e5dea605175a6bfd43c937331f4c6f603d9e8469fe63e6cb8d450961d817b2db426badb54631502d25b1c83103ce838c9029ab1e53183c359122b3773cac69adb54649924b6b7d1271d39199db3ad1ac9810b931cc608acbd775e505090d9e4220cddc206613f11445ac4491ab0e2794846d46968c70e11882702fb8d1522a7594645ac203905c4b9c738e06d468418d26d41ac8d332a7925c9c73ced73827b9c436309f70403be7a12fae93814dfa8ee93bfb9cbe5adcf89203041855082556535e4765545959516c97c5b622be05ade6dc508ecdd066320c97b4952d39482eadb5e6224449adb5d65a6b0d93e46ef1aba7aff4ec4155a8ccc842897b8b9af224a40d4dc611dc111d62716857458e02b0829c22976684ccca392380c906505c5a59b478405a97c4bcb8906dadad5d15640feaac43ad611525bb96386a814ceac84f4887436afcfada393de444decde8456bada5b24fd45aeb1db02b5619f418220a82d35a29909c4abc695145d1a0bdccba3640d72966da96585870912d4038b5947a58ca18f689c3328314b41e4a6aadb5d6bb6986294d5bca90e6a46a6d9246ba51e462e30eb1db46d0180a3242085c1107a240c93115250a598bd14a4d1157eafc4704f098c4b851e5cdcb4990589b87163da24e6451fa9d4029cc4ba4b270f32addd2536badd5230f780c7391311d6aa14b88afce500b429c46275b128cbce042c4891230af200fd0c83281e2060bac1a1fa6fabe40ce391f6acd39d781c98c9a4d872a8c84931811312f1500ba2176e40356dbd552df4337cb57d9b38ca943677c6ca18a318951b64eb91e47b8c895a1f87116ecfbcc8584b3ac3934a196a8267157489f5558c4168abbe5426badbfb24f24679818f448bd9aceb44860998bf2c2818f2745866d504d5c377c17c5de7b9323d788364c3361f28674406af25643cc040a2d1d13a844f16224c90c0f1bdc1303e0ea994a44081664022e0a921e105a303410b14528d4e21ebcca6b02cd0049483576dce842925663e4c2c3706e0712b0847066c812066fc02a2c613f62b4bea0d831b643480cae221f891a293721d2d19b198f195d8658741971c5c59817b22ad38a8c0b009dff9e04bd900c6744722493485d3c967d895f19484ad4984b8c4d4034ac58bce032923225891557901188a6205d57ee88db25c5af0c284a4490d12811266ac9b25584c30aea448e2e1e57c4d2de7bb305b34f24c9710b90beb7606d09f68c025a54116d80d564c690046001124246902126b8aa9a3608b186e58dc9cdea8b88ab1bb01b545340265f55479820dc20b3c7a1696de69d632cef1c5e0fc9182e737a902dab2f23dac6f0475388ae214a529819fd18a95203aec95c9a8c0b42a34965adb5d6239c4648a9284964430a09b02fc0ababab9f94142a5d6d349a049d48d2f0416b4d6291d05aeb095d6badf590a74950935b171d5f8ab07030da8005d88e2b4d6cd74384240dc6809482441a640d21914989f10ba115b5e6bb404c3bfea16c94e2356e2be3cc5600c7115c22554149d6932d59597a4009d281b62c28403956600dd142aaf205c6abd425590f3ca80ce1b042b2c6c5c477c513338968404042b8e38497a823271b8a20e150e1042b0c88cb8795124fd25a6b2a4e65a9d3b6bad65a670d09eaa25a02c407908e28b01f28a8b01c7999d29054a6e1a8e23c9b3e9a84f838d20445da4f0d6a48b464ea45972c0d9c73f25291731c01202ef03bb3a2b286e5618371abc599d0cad987221f5f825c262773009c0a9fc34d4c455a8aa8485ba1826cc7579c980c10e50552e5ea80b164092e05d7972c1b398678fd483204cafd01838c0a0201929952957d22c953492edf9614711f9c6819425192011652721eb24e68192952e663096f146e0cfbc4211c9ada1bc5bc252ae724b9c2b65b95db96f85b7252c92af88bc4f2030b16962e9eaa4506ccfe11d3e49679ea1c952371426048433d2860e42b4890998c2767409c462b20505341821b4cee4bedfdf6e29c733527ec13d5a448150237e0588890e1e2aa484db735006a05d04510105da634f01e7c8d2cbbd0c8ac90384b2104c611d16d0a2ccee8298ad25097d18dd4de7b93239212f3e4a4d020b7b17443854c708c6c37b9994122317e4d5ede664c41c2e636222908902d54416e1453d90b0724ef1af26e78bd9baa9b2f77849cd49aa8010717ec14a4f22ef4cae984c939e75cd54dbe0d586d890a538a9a3285f4854990880fa7b42525245c1e5a6badb5862aa5ec9992f32267ce0524a22918406ca400ebc0e34993d80a28b032332d38740b48ddc18742b1382d7241293090226c069b93164b34b460c8683d3d80e24d0b5077d07602caf9b9d8bf19397f69ad032467b9f2871073cec925546e46a5dbf0c72b4d4e84a04823e2a1a109181298d1d987282155daae0e0a7a5fe1de572b7ea39421101090882df24b1deb0d1f0d47c305559bc21994d3a4da23d368449036058332234ca6ca62acd8b06409657975195932f364138506649cda1aab9ccc1e175d4f408890646d6100945cd205422530b610c17273286528c21c4090d2d0a86c65192271f242475a1290b2ebc45b227b812aed256613226a5290825f9cd03acc28b205b8620957d50f29122e0e9c6dba691b4099a60d480cd35a82db62535203469a74c5c55397361249ee0d02909a6d6c5ccad1b58f1e5d975d05a5f60e1ee231c1cc99d9a7b5661be3dcb04f8c9323559473cef9da6b1c5843d298a973fb50a821c7988828566647538a0c31811a9ff48d01545993b9a0141b2a57c138e75cce29fb44724fe9fcd6e09c935ce35826d45d50c55e59c1d5508abae00ac293242baa0cba8a5435ddadb4997ee618622b8adc50178da1383518bf24fd6b4a834c2b84aa30a1c5dd0c4a33333333b3b33371c417e79caf01d7826bc29e1964d4c03476041535d818d31b8c9d9c73ceb994d61c65915de41927b7c6d70d1839a7b7c5e0af2c9b7c2849b192624b6898648fc1741091b436d6a405c70e104ca3909c8e0865b8a4b590058439356585a5a022b230e48406dbdc5395131af438b4c197b4d61a051a4a6aadf51b08664075d9a1f5c16ce8c6891145ba700cd980413a60517a954a157221e88262ca1a0f284d563acb0059544a634428b27cdc24ad35b964efbdbdccc019a071ce493327c04d3e41d35bfbd81ac7225ff98814f9f553ad96747cb8896991003e90c5cbd3970624c2989aa00d15e1028503c88bc6d2cdb682ddb24f24bd4c952f9158994ce562dc6dc416a2b5d6426328a9254c6bbd4de87e06fcb1256840b60032b2d2398a9b988f2daa2b222a5d566c3d9921a7e3458caaa6b3b3920d691c8aa0f264d12c2e460f293b9e0851f1d4c012c4a5048750b835f6358373ceb7b24fe49c73cec7b42bde100d63caad840c9dcf469ef834369639da0a286bc839aa83b65b1bcbb036e4a4d9022474c8800b4494539915165059aa3c510ad1b61435e343cd95c1246a25e9a0b2838d089136116c8bc9b02d48d9063562947b97d194596288ecbdf7c6b0f7dedb0d080868c4de7bbf8d619f3824d154b7de9bdc03e1747de5877da2c6d033e035ce39e79b0bed774d4e18259108490d875af00114d8bcf9529065be7d986f717a2b0ed3b5e67b34332ff330796dd50e1564f3335ace727df716f42bdedc8bf14d8a6f32ff5d8e90b22deebb8fc0b6b8f3b58eef5f76d75e7c4dd687ffee2fc6acd93b54803fdf9cb39ccf7d0b3ace7c7f9a3fcd6ed8bcc96a7727fdcddf34d936af61f34d33ebacb3e6ac0ffe9b654c996ff1d92649f345f182699a6b7a7bdf9234dfaa33d9a777f833e78f591b08bfdf7c0af9020512fec1bf83f57536cfc389b98fd9f601f7f130a3dd1dfc1967c0588220b5cd6d5eebe6d7acbbb467f798f5c15ff3ba9df91444f896ec362fe20b36bfb2ff6cabbe04b625717e09664ef3ee510a19a312d8169fe91c64fcfb1f9ce906743caa593184d50218473f5f3180bf158797cd6a38a3f5fb3ceda9c17fb3a27fd77d7ecd2a769bb5fdde8ac31acce221c6d91728649ffe076c4bf6d89674c0b62a0ddbaa3ab6edcdb06d8fc7b62fc3b6df63d81607866d71544ea6eabdf77235aba59fbfc5672d5eeb986d39a0e7eef1a38f75dfb13ee9f337d16b9aa689a62c49aa6a4fbc208a59ed4777f229e40beceba7401c13c0b7cf7edb03c0b76a005e00ec05669ffef603409200505556ccbeb697d55a7cd6392b4190faa3fbfdf647f7f4f7a3bb0be49fd5b2da9db01f7dce9ef57d81bd5eab762dfa7ef410fdac86a2798d020b88437198d5ee8d15a7fe5dbcfa3a4b22ec14ba7885c4ad7f85f50274f1ea48968a8e76dd6314d8c730b26ea18b576148fd0a0caee374f10aacab9fe20454f12a2ca89ffd9ec3ecd4b5fa62f9e78b5de4fc1caab91d7b665b0e6c7b75e904f7d3b407bd62375fbcf7cf21d631ff3e9a6eb8791dd30d2c19cc2bfa679a73fe49571f1dfe1dc629fed4cc3b5e7b6726e0b12deeba9f615bdcd373d8cd3ff3b7d7bc37b33ee9ebfeeadeee0e1558d0ef9b2c36db11f48edb73adb7fb571ffc16f4bbb6b99b3fa60fc83de57c15cdf3acb73f7803b9b777821677acb3cb3fb8a7cf39db9ec3be9f7396739eb23ef7f1f3f4cf8bc67c7b7ffbf94db5ce7cccde359bac8dcc65db07e0bf7faff927d9ddb96f660982d41fdc6ffb83db73d8f9a3eb2eedd1fd657deea3afe33059c8bb9806ff4c9af6e0f73159dda398260b4d7ccdfb332c2f0b4f1488ae77c54199151f6121c6d1afa3e255bc13da93ace3e7690ffaf9f77a7fb7f2c7ebfe0c398b3d66457b8b8328a9aa3af2af7aaf8ef521df7cdd9f183dcbee8e389a2fe66fef6fff5923010854f7b84602102a07b4a3ac8ff9fc53dda37fde5402fc75e92a0153fdf492af63dbb1abac4df9fbf9af4571305f20b34fbfc638e66f914bbf9ffeeedbfbeb9c0307b4a78feeb4e7be4fcab6b79b13a0fd8a5c7acb41e4d2cddfb1edfdf5fded0576fd64fafab26d073a74f375cf53b6e58076f351b61dcbee8ed8f98fe9067e0efb6aae38788471f493ab8edae2e003ceced9b3ec4ed86c768ab9345b6395fae666925c403a189b060fc3c939a7b98043a34a15545b0db9a11e2b19312e78cba2c523aeef3b046f69bd7574ceb9ca4b5487ad40eec30826434044d828a9861a0c9991015d79210900d6b375cd01811e5e7051d2b1446e2dca1a932a38d82b84298dcb2ddc4aad758ede9c735e632b0b915373b4d61a6b8dec58675b6d02044384a436411bcb1cb25faf4984b2b71d4131610c2842e690043da6fdd1e8eb2e6b7e8a2fbffb634af1798cee0e3651076cdbbb976d7fb48e6d498ea61b509e2e1e23fe33fcf3aa7e611cfe3aeae6b8fccd13ec89f9f3cb157f86cc5b772fb3a61f13c3bfd5529dbff9952fa8bfece3df92ad6a9a5f30ff37ed21fbeef96abaa9bf7c415dca3efd3d936d7baa8ad91fccaae33c46473def937cfcedbd240cc963c4d3d674ebfad5a57c414b659ffe5e4f5597ba7e1e237dd4eff3d7a59ce43ad8dbb1f31d077dd44d2dc56394b23c465a4a4b712dd5358fd14e37a03c47ea96da8571b4baa56ef118dd097b66487321b8f130aec2c05167a67b6ffde9ea83f7b9b7dee98955cd5f6360bf67fa699a9efbcfc798436f974e705f97f6ec3efd11eb887dbf1006629dfdf7d374c34d37deebee49990e15a427527cd47d78584dd3c7f8874fdc1dfc1cabe83b459fc7b1908a310de73d0d8a817dcfb02d49c2b0ad4ab2b7f3d0e76d1c01de666d605e7ffa986d2f867ce1636430fe619df4f7e3139b73fd2d06f694f7974579e9d38c3329e6cf59de9b180bf1d29832dd70e2aadd8a8530c0ac3ce89a0163217ca2e327d39edd7d753d1fafdafc11c5429aa729dbeaf4ee58fccb17d2e724dbeb3ee46ff275e8eb5e4c23a0636dcad77f1ffd7c0143f68dcfd9564d592c7477d65eaf3f893ec730e216431f5fecf1c742e89f8f7f5868a71b4ea089818763c08085ee04cd8aa3cc95593b6fc6ca2acc47d3b4cd2a4852679d71ce69dae3e0f3599455a0e986cc4fbc3a78714606e6cf736dd5dea1023ccc407eb659d8db1f8c571fdd5bd0af48de9b6b3e7312e7a2bbc33f3f38cf1eee65153dcfc8c0b0fcdb9cefb33ee4eb32cbf39ff94f613ecf35677c3e0e667ebe88751e6c6cf3ad2eabe8fbc9ac79d63c6b9e9e36ec0fee29eb93ff3eff56c7fe8822e73cb3692fff59c3336b5393c1e6e62164b6cdfccfcfc0bb93ffcc39b7115474fe0ef0f999f5c9af3b39848fb9791e4dfefdfc8cc0729e9eeca3e7ebfcfc7c08ec0d7bb2366c9a662cdd9a8bf29aa1666500b93a207f2b561565a083fc0fd6f47bebeeefbad177b03ac89f7dfa695852ed31ac4a96afaa97647dca3f3f1791e425337beabefdd1e9eb589f53774f129fea7396b5313f5fc8907dfadbcf4577e7fcfd774d2f99fe6e15310ec9595d06febd1eff56edfcfcfde4e7a2f3756c2eba13f6a78f87e9e7a2f4c774439a558c6b068ca3df5c19505555d4a1e63a30c35b3ccbd13b9d599665a9ff07fbec922c4b8c35c9fa5c71ebd7e69fdfa29fa66c9bd3f59f24fb83bbcf6d5196fc936575f9e5df11e38cdd7c0a77a734734ab6bd6a3ac10ee3dfe5532877851cac536eb8eb986e284554ed1d2ac01b3f05bc5bb1aa974fae3ee756f72e9f7c0a77673f89a1b7325f6295f521ff547fe32f778ccac6b02dcee9e5b732ecf9e49fe43ecbdf64c9dac4bc7e73effde553b83bf74b36a7b732afbfcde9e6a3bab4079f9f9a5f41b7322cc668dab3d309d0bebfc2ddd9d84c3798e2598c8e9aff52e841bf7cbd9a7bd5b7c2ddd1ed569bbeffbe4ec7da90afdfac7077f6ebaf49d78d9e15de34bf8299338e9bad702768f5ee54ecee7894d7ae10351f7d11eba0f7d73550f669b59ba63d356ffe8875b2d8876b68ba21920fa8b40c590b62ce18c51100000001b31700002800088683b234d10335f614000a45ba90a498501a8b47426138180682c100502000410000401080611086623196ee706b24bb86af8cb951f749e57dbd23f830dc0547252728c144e4aff9753cbeb4de4f851d4f0661944eba0797013593b8d0641e5cfef5f89cbda4b1d905624a450bb6e927a63fad4b85ad3dcc4544420e2864eea051610dc2d551afb24cb17e03b471e0f2662ad6c2c26d41e922297d75f1456e6963d95e63572a676ad6f8f60c8d9dbb205ba78fd6cf2e8a6c2737141d1c80b00c9d77eb1a94988cb8926dc1555ce6304ebe95069bb3431c29e66b6f7591b9948ecc42599464e86bc15de05b9109530399d9d9e1dad18c24b3261ed66133f9e78accec9d025c3788cb18b357c8f5b9f8792a1e2196bbfa8afa4eb2667ee4b050b01530194590eb029296fd6f553d09aa49e1e54f525e2d05c7f79ad572edc19a7c89701756138ce2e021da0c2f2ee11432e0356edb228dd6ae3fe2b322977529723b786be1fd4542129eb4cef15cc3d5b175a643a5c80a2611fb2cb3dfd6cde077c4b8317836e520b330c84e606fc1d354e21e90abbe792bd43ea6a3eebbfe50221cd287ebb8461ea524a5755a0920e9a2a0c5997a4b3f301af1410aba3a6987972599fe894ad74872bd5cff0c7997027a184b3ecd0f5ced8ae403823d001975000b7c84e4224c38bf5839095941d5ab20c146fe1067fcac790035bd52c8738d6bfe3ac7cacac2a624fde8ce203756c66b0badeca1f04a04f40ed619cb8b4920fd16447d203e7982bc0ce05c881063e6eb78bd88e05793e5f74835726607bf24264617fc0e46f3770c2f8215370e9eea7a0038fe09a21c7d2f362f24142b0ebea63a8ecf686e9ccadb95e7a7e4ec6f54a3b40e2851dd1af908e0664d5e69bc1e3dd0c3e83f29cba773a13c4c24a3804d2af3364af28bfaf17b30c8daade85fd6644b20587500abf369d7cd4c6b87d4592225c411f5f9ca14a72304b53a5758e9c07449319db1af7c0b5a9065df98b42acb2a0f7a3a3db607fddd1bf4d6f343c6d46be22112a9feb21588858a6f492c46a98c315b6a4b75bef4329955a22c9ce4da732e6cc4e8df314b9f8c919a96228c988ce5f4eb8d7832c4dbefffc0e9e7e7a4a0528b825da28a7cfc6427bbf5b9136e0c49520da85996387fdc6c0e1969ac24ff12e0af8863206a47b665cf7f88ac15e8827ad3104ed7d58fd660d864770e72c281c7aac92ce4881cc0276989a66bcbe9a499c081ffa81e7aa809a4940622e132dc346a86ffbbff6b1c08908527dec3ecd8a03256c277e2c4e14e4c586d484419d45788d48470acbd97a346d1877912bf1ca06e2d3c7f11548212a5232b45f46eee012219e04c2256b98f54db11aae2783d40246f86a8866a7105c5787d7e789f3a32c79463a21c7e96138a2b456d4736bd84d09c1675ce6b5e46b8633efe26a73c2d7a2d8a6ecd7fcb11869af7e948da6a3c7eae4247d3430dd5c961a2abfda960f43792da51f71c720ceade781ef5cd3281eb4cacbb14530989f3b5efa227e636a6175b59da01c7a8f399caee74b87fa8e485fbd3c6dc2ed3417745a588b7c3c8c86e0d1d9a21a83816d8608b0a2bb82c0a583d462185f32a40ca9659ac42a860a2b70cb44fba35c09a38b3ad8e6cf4ecfc041876a1429bbcc5e510c3060476150aafbafd01e4c32956896bf2b30a21228c7bc8bc74124739dc59860e06e96aeb3b2b89993cbf213f123a33eb486b073087683758b01b17f209610703fd70b8330939ee00c9157929c63d60944d14596ee0d7d9acf28a5699c7d8605fded0898c236213c19cc656fbc9d46ca2074f55dfeaf83f74e5162a69d5f54e639574a89100b3782850dbc47ad8e98d6f28d2814900a74680358b763b8a7f6bf8400eca16769ee5da0d0cbc6d2edbcd6cf13edc07df0c6e90606f21c609f034cbdf022ad220fa1518a87b48c16659edb8c68b19470c00cfae6c075275059944211fbbbd7bed3ea5ad8795d178263748ecf521723780dccfe47a3f3532b6eeb8c2fdd4a8413ff868a41418fc8f998a4a886172be22896040875832ccf48b2a0ce46a95cdbb921f9d20c1caab5eca025510e60d5afc3619c445db4eca630c8a1d737bdafaed6c0544a13ee2b1ef6f9082b7ab30be0d0e6edc0e5c66c7ce27ed7cdc894ca1a4a0f73e79005ee09545ab976673d369bc4a7b817da3901929c01a2f3676d33d3558a59da5467121ffc89dfa53e20c7458be408ff96923baffa33641b84f0555e484f0420db5a20b7be5e6a928a0d3e68998e9230f96a9abdd800bc19f6ad7765bfe9558196ce5e103c3081a0f856539561e7b51a4254e3030686c5d3c5492c04cd0e0c007fd5191a8b12dd8bee11bf838aaccd54637821741eaf090cc39b14acc52ca6c46d1451d17c1139d1ea665c254572e4e33ce513bd9e389d072a7f9bb0ee4966a7292c4813fe67b57fa8b99fc49681a36b0001703003c3f3405de0e2c776da18b033484ae5d10ac8d9125082e40a1073bbf0c28bf7e4a146979a913f54e53e6e2de007e57a516bf264ccdc5106dd488bb20be7e54a6ba47c898858f26d347fa98a1d581955c5d8cb518adeb093d24167915624649d41c6b25e3bcced7e62ec93ca7340366731beba6e9a3bcf59dddf09cbb2cfeaa29850253b8a7a57953ba7e536664a67c63726f78fff27c7a69c10b0584799b96eb1f91fa5c811090be48c1ef4ab231e60805b882affaa2532824075ccecec9d1602ff4c8a5cf819a47440f6b575b2aa7244bc1d4fcc74d7e14ebc26332ef9ca035feda29d5903f1ebd2dbc233d29a97c86beceea16cb26c5253bd9486c79926d570291e73ab7e64f22ad594239751f4f18f32818612681399ed0a147b4c393db0de22d912cbd9ba4600fadd5c4f3761b46115358f5289549b76611cd40b9227bf236cd7b2b5f909dd5df7fbed39830ea4087ecfb6570f9d76aba35f6479cc3cc060924d857d67090c5a1d1d60ecae6b2643766dd6cc2bcb96a8e5985c8149edff7114f0c4dd02027ccb6f2eec561124abede3ce73b9c44c7d74a00dd59e1606ade37b7d1ebd976da49eb022a9cd8028712a2d20ae14fef6744f47f457dd2de16b4d249960a8a6ca914646de9d41f1e9e651e0a766727c6dbad1c7dfd8f8865a3b539f14c857de4f12309606f73e05c333eebc75debf17b23fa3fdbb4e8f733bf3ac2ac17925c95ac7d7a4c18024e8f8dbbe23cb78a9e3d30dee3eac87ef915ff1ce40040e2bf218cd009c204a2ebc9963066b40034c43fb457a81cbd467472e502a71777890b619f6ccf189952ae552f859083e6214e6a69c891fa9ffce49d6fd9b8771d186e70a2536433ee9758fc6fd5194b8eda1f7d650e36cfb823bcfa7a0653c5799e55da2224ae1fd97050bfeceba57748e9e04c496b9f09854cd1e8c8fdf2ea306c2446648a2a8f9922ce97a3b570eac6b0078f1ea9163c01bedd094b041a2de827a54d8271ba6827597983bd9be5f2d7bbe8604ccc5f716fb12910ca2ff4bea038c6b2be5637543c3e91e7c854620d4d970bbc4e473fcd91bc547535fe54526a4783c00243add16b1e990a2732d1e63aaa7a08b9609868f9c6aa5fa2a32c46b3e67fa27601e226a2fb9e915321423ea267773c74b2e9ae9b35bae0d75db2f9d5f2e9644b88aef0b4808dcb2a7e5edf1d6d3bbdbbca613126dbf6ea46fd44c63b0fc093ce1897049d3c379161a7a14fa63d91c0be9b5ed818844c8ed9dc81c4b49a6aef670601ec19e2411278e98270d19cb9de188132cd4cd7e9ee6c4e44ed693417ad89c56785e6eeef745497c638bbf91b470b4aefa750263d1141d87aa9f135e1aecc542a712a1f3ce9bff76f0ccb684b78889dc3fa7c46e77369ab97363f03c713b12b95cc3955c50640a47d4e67c07f429f66573fc03ec58cee88eeab2d0451121e37a656905761f40c9d4886fc1348d438667a6532ec2584c9953f3028e406444174c45c1003e1ad82345e2b560b09826cdd19fe7ba56bb8b6c2e2901eb7646cd051ad7c7ea3fcddec2db90128194e204ea2423613c9699c949eccddaf77bb2d4d0660c2c37d1d79e8927c46fb2f2b0a7399a83dd224d9b369a09d866c27d17ddad392cee25aab8389a4f1667477fdc382e95195dcd77268f5461e7839a0cd052018aed1d5d394b524a88d252767129ec6b553232d96fd88cd123b5bfabceb7b930abd63a6d14bda2ebafbda6f6814e7a6cc182c7e37396b5eb374f97c208905ec936bb89195814f9346e1b9bb56b1dd69093b68639db412ad17520b259dcd8c1a5323e97842cd68d271bfb18e46c9ebd88459d7c83a951dbcd962b46c7060da7d894e87ca215a37a57551c082c005b7e0e01b670b3eb9c2f5785a625def435a8c25cff04ae4e86a17dd4a77979abfa80f19314065a545c405db15cfe0c90fd5a840f11c2266a0b4c7cc6ebb67475fa55eb6110fb5222539a05b5f79c60cc5a94d7f2e686ab53d73fbc8314bda99afef07cb2ac97c53290d4562bb45cb6e4736f1198d5a8e8f94a0d325a3c589cd54a4c715c2a6276c730a135005faa99f130e6aff54c022d01b2db940e0ef6e21df2c48e0793c5e74a6b63f2e83b1732766aae9db2f0a7b8aedd9d9a8916113673d71ae92a5713dfad49a97c34510191af44361c8d528185f67893ac8d55f9c436efdf2453c47485941d7b5666ded89fa1cc0f9225cab8659d4df833c087e807f17fa1219c1ccaeb8716e9612446a0fa356df19c88843d5d5acc1e7f6abe4568e4cdbaa73c8055e1dbad21f9168b5f35b3577adb3307fbbda4de561c9365252d883baefd6c668271ac024a5a9de2f5bb90a791cd0069582998c8717ceb40a6c391f5a9b7a71aec4dfde39f103ff5d686404a2bf7905f9d4ae820893df0ceb554dab736e3d3d773bcb3bd427d328c38d37d0e770b72afae10209a27367ea7aef37400161e90ccca759b67f573c8b284049391ee4d4d8d1a0640d6a7e966464d4d33a51996c61c3e74d2acda62a9f5dfda4d13982ee48294030fcf2de9563753ffa7dcd80faf464779a99ceb4d0d1d777eb29f0dac749aee6d7b19c2fcbdeabc84e91184505746d18d03cad9b6160e867cc99abcc2c57f93f1b9b1e95692a45a3fc75a7cf580addac1882cf131ab10ee379b7c0e22234ba745a9406bfaae0d1a1e4ee4ce28c072ff25c5a6ed3d87e21157307c82f2ff48361c54a8fe9f9909d50aae91dbacf152baac26314fa0f64e180b880e2b6285b717552cf22b1f56533c1048a6b046fe230d80786e68a70022d0b6427399d9c6c7a3259022aea4a4f6fdc7de3b34afc19d85b98f00407ee57618c6db2a7ba9a00ad52a7d60c171018f7168c47cbfa5e511afc895456f806792aa69fb5d7f857a2ef80283d37e88288184b96b2ae00d3ed4704835c2d0e992b6a24c0c279284f9d480c3f8085e1eddae8f066e414c8df3ddec5ae795972ee11a473a45a1679d5273dd93d20553d06786e2325c25cfd6fd4ab373d448123cdb1521e9c26f3aab260192d9ee1c93c8e021fce4abef1ed762ccee92e71c5e92d77960fb856c479c32c1b0571d9c0560ad0e2324a444bef6eafe3713c0b852ad0ff5bb60dda7de28a6b5f48a5e80bcb3b2a45e9f596084971aaa86ccead4151eb4b7d8cd7005942dd2a4555ad0f17e77b49d0c27403515223918de91a4a268d0707ddea0432eed2e2a9c78d6d6c378dfa9442d904f14d6fb811db28b57da5c09a169a9f7a2316cdded16c2bf674165f021bef0766e5110d6f38315a4254e7ca3026d6865a270f91575107aa606d20805478d2e5dc741f96890d181ed8459210b262eccebf6bb0f2b9b4cc6224637d89ac84157ef0ad74affda6c47ee731655328f3618955a18f1940afac49dcb94d9b746a0c5ad4f86d142dfa9b5906cb21daad96113f3873dd06825443d1624d098e305d687c48bfb6e4e8eda47555a00921dc0218d77b3ac8533afba1123358535697965b6d20eb55665ae29f47b6fbb93893d1e74d3686ee3f0a2de05d11ff27497e7c8c54c8236961d774328ba8ebd09a7c3862ce5495ac1cbb5dbc529481845609eca2cc9784e3da52117d9b6946c925d068fbc92f68ae9de5c3e810d7be50e14d3bfa902b2d771524f44b108c2884602a925983a8791fb28a1077e2cd2626c6d805b53560a26d4d8d28e9f03326fe204c45129ea7bee6bdcaa15717acc744f530f2a0ed3a2248e72d38e4575cb4f8ec33f3467ee6c15104febe44472f940bbf7a3038217c0f35abc0c6fe4d0e64ddc577e4e35887ab8cd97d3a1410c88791da5b8afe0a3bd8570e2b5264401d0e484480ebf15af1d30c5af309bac83f3634a42665f219f543f95225e825d70d7b1fb37a545630a083feeab24b2fcd416516d33888e401c18eb2e058c8e8586ec7450a6fdfcfda03df3046f50ee8874065d32bee7ab04ef527494d46c6da403183f9db292df2e97d95075027eadfd52ea5ce8dfc8e82876097d83434d8de267c5dda69aab532d545a2122bd145eba3e5bb9b331b6c67a1c1975b1bf8682e5dacc282510116a32dd9e226ff23600f34910c7c174c90f591acc9d169c96c4da3f3b7591f2b40c584c7dc429d33455eb34e1d38139335ad408ff8c505767762488b27af431daaf44a08cbfad30820c3c767e402bc4a60be177b0529683a5cc8db34640f6694e8ab01be1d17289544eaf6b7492b2f71454bc38693109b80e8c25fff3fe17a2c76edb557401cc00fcb6c12ddb17504f94d519020b9234c9e02678766c9ef0a25c8151583950a076a957957f66e374349409b49516352a0eaa47fba79c44a2d515922db3dcfcaff86cfa6736f8f0ec2326eb5a0a8bb6dd1796b03ac074a8ed1eb6e1719344e85e8b6c1688532752eb4e5340cb6e1d62002573e4146c3517438ac79155ed6024642f66d47913df842e2765b24b81b5fbf77fd017427d8178408686c34836b93371fbf18a2ac6fad7821b8b1f9dcee8afd090c67274400fd8b482dac61ca6b65e5e76904c9812f3059f9f790bf278e359f2f41338cba4e829617c79d09c862e4c3d720b69b85e50416fd16b377044b8be08bc69766e4b15a60096cca5c88cf046055c84003db853eed7e153c59f5dbcc26e7aaa9b02b6e40475e0c5c05dbe84d025d2c92ed2f0b2b2606565dec9493adea72fbd3eb0d753921e9df8274532cef9307b37b3d51f97128e8440095986a378f3057c7594241fa44183962dbb648bef6d3810470aa897ac3bad3ebed8fb63914550be05b11e9611a8fb3950c8211d3937e96c1432817d802172cf0ca268df4beeaf1b8ecf8d1e906dc41654d8a3e616136079bfa7721d74b81e8fcaba8917716a1956b8239aed39966b668ed378a88014d95e02a268a503b25cb1ea845f159dbddb1e7266ee2158ed48ae019932c83ad258194d8ec953ea4c50d834ad10329cced6789b53fbcc5bd65f29f892c745558e08dcd47a98cb3050aa28c02ff04039904940bf1b032aa76282a571a5d104172d0de2c69b87bd2ee7ce5635dfa892c5c15090f177c8c7138a839d4b5c22c6a3045e06630f6695507904a2f4f58261c0633218b50d409bfa92d85ac726c99311f83e83f7164f238f1e547ac401bd03d39fc214430ba89ae8aff05326e8e4efae50e05dffa8f8229ca26048b83a2d5621c6d80c86722dd4cfea591e2ac1be8f921b4dd0ae8a2d28024cec64a7c38a9a2de045c54226381717eb905b1a42d38ac4c9fd23e2aaa56731f331937c74113a67d367bb37a5dfb95a15f6152b6f81d3e59a19d8de4a1323d4ea21624c1b76c63b5cb3da7b9d1b612135fba3a63cf980d12315639e98508800319b852f805322237a352a00f2ee9cc830bbe01566de5b1bc9fa257bfe4ea993a0066db65c138d3d2d809fe8f68d861be9d5a94751db4799f832be1e58d6c9d64c985fe5776923810adeabb3ec1c005fccc9e64db891a58505cc72924033b92281a41b1b856496e83f7d0fa2d671d36412fb4c8d1d5ac84d27d09b7a477da7f86f6d1085f2604d5a2686adf949697d04f0a3312cbd041438d2b4c58721a3f800bb9261143bdd7807bad5b5387e69da934a4ea8174f98b27305060200b7a669fec51b91639cf2ea55fd216c4666e5c42f6da13dcf01625064b152b2b769eeb48c238fcfa939d2fb9741abd0d88b3db52051ae345e0f814c4b9c8ef3bf9b7429ab6078895d4f2634fdddd4cf7450501cb5689fcd1e266fe5130314b2b652397bbc10d6d9aefd8aeac87ccdb7ebc7eaa1da49356ecf2f4633bc27090bbd76a0838c5bbaf1b4ccec73ae64500000215c2046337250981cc3a3eb91df95a49ef4680cbffc2bb5cab591e6221882a9cca21456bccf97ac3090917c9d7a7902ad12a9ea4e37cf17297c66803e770e5ce82472413226481d0408e8cdf79682cb9a03f2da36f5ae80d17548ea01ca922ae461546c6131689b4f6595ea0493baac51104a9e2b4a52aafd0203e18381d2aa804b99bb45484637f507fb475348c23cca44ad5e5404986aeb4e6dbcdace85bc36112d5c80a2a6180db4ab809b60e47a0a2d47baac2422e156d67293d276eccba21e09fa3f94cac1ae41c5502a18db51e8c5f8ba76b62b799e6d0a2aa406d92b2147acb1cdb366d9425ca3b08384d56147c09d3793677cb0454776ae3e74d85a65eceb4ee4597351e7a694829826daa8fd4cc11cdb3e23d3850f07314637c8c3cec0a66e6ca88e48723d0703e93de7cb3584e97918e23ae2adcaf03dcd26a60ddaa930cba20c96d8dfde951686774ab9145218fcc7376f4d11790468eb0f51b318dd2b5a21219bbb9d6d18cf031886c54fb0bfb63f2274314fb067fa4447a11920c6b9412833071ebe6376e96a7d5fd769df53421702f19e178abe2f9af5b3542c5cfe3d140116010d26844a668b0b9526b124421bc5a4e0ba88d2a5f84aeebcfdd199f7611d253d56e5e5a26c6ceddfac5d0c5d8119999f23307028248c2058389c8c7f36582d5ff4fd07c6e6acc39cd10053dd0c05b250147b648c64f5adb8de375268effd72e756bb744bb2ac6bca5ea496e0dd804289f895776d135cb862ea6b116b8e846e890869bf81793428627d80d36214c324afda99317d43f2b75c9a11cfd85e408d8cabae6a94b74cdf8194aab94a35e79e1f029400a231dfe9c6e267a7ee61ac7c5e14c3a213d2251c42412f1d1261743454a64ce510c8ff45f5d1e5d2e2459631bfbfa60224ba9ce3c2e260be16e7b0f6e802e7a5314f1f1e6b11da08525b05fdd5326cbf2f2d7660d39deef7e5e1638cd72ff548522c56c20d3328259d7599f95de98d0e99ae0ef10693a5884bbfd9d8ddeb1c09755b5864412fbc83344ac4402aeaddc8674bbfa6113a41232805fe92ee1ee5559758d6e0f49dcc04c0679006a5cf709238fd203cea8651756a122e07e005cfe26a2f1431ec0405480080928028f546f48322054a851ce42a4eaf718c80c53dfcc5025745b9ecdd14783b44b5e1697c94b18e79527ec856023e0ab6c858b4ce8127c3e88c6839af407ea4ab51d808a558b8bfb16c02c4e051f3f255e200c177c33514cff357d9146f289eca57de0b977fd115210e05a13ad48f28cb2721704b505167035e672426fa0c003b64e879a2d52685cbd95f38ecf81c53ce560155415d8caf918df78c0e1b8f9488a3ed6344edd1380ca27a278232116d213d10c75bb56df460b6461800acc14a800a3c78d64f4245b7ad9dd03c8666b0ce77d6fe3cce961d5949fb962a2dfbfcf3f8232c8f14700c53e82f6cd2272570f0f6728180cc53fcb21b3baa3f993a8628e26ab20580762f96a38c31b1cb9ae863c9d0fb0df5d876e3a3a719babb37cf731f63bfcb01653e418c7120e9e917cbe6e7f899987af5b568459299e44b312f948c2262f3c66b06be2e8f936ac63bc179d794658c7c24a2e05f71b71c049423947244008ffba41dec5960901f2430bc4554baa6225dcc5de100beda54ddc09848d50bd3b1904bda579e5a301dbee8107c98c655945dd7d126aa09576ea6dc38922988838faa9911197018726195142128201ca0d21af23cc7b11dbe5904b00dba4b5ebf32ce250bb5b10aded79501d497cd6112d57e5b98ba8941c839c74b5309414450c9062b5053f283a78348a7b9701ea1dce0971ed0a56ac9d0c95eb1e85a0344681fa4a54a33c49849d4b5c48720387d27526f11449da415c90d15c6041bfda48fa5c998667077ef05c353a8d108ed6c3505fcc99d36046246d594d60162fafb8230e72e7ebf3cf2545df23b348a42a738af67cca3bfa3059ba960eb1619b688136af979fc3df7c8804d46ea8287618287ae5b3c4564e0ba6140ea8d1090f7848e3cd801ec9d42131a1a884277b66909b0862300b9db1f2f7c593522a401b5908290d66baaf9bbcb305e245dbc33bf1eac0fa4735181f919630742c68787d9986322a5e026d93d21309055b17ad2214a5004991d62cdaa4eb5209d0f41b5cc99af318633ecc92d54cfd577499f6c4c1aa557e3662964def6969d2224014046d4343d33723d6b2fc4e262b99fbaa5beb0e62132f842c9cd8783a871173a842ba23959f9d5906c64a56ea0a05945a38ede7bcb492118ceda2e574a7b5c8aca9e5f04df010c5caeef6e0a37462d716025adb71f7194e107d0948681cdd972af1b6668de39959a39bc80967804e314bb8f6792c62d3d272d0cad9037753329a6356068f8ec64cc19bd14aa9991f6d914fe0ee53eea9521d373f2a2195139af6b283073329d235301e8641349b5c789e1477a0c72690d3c48e9c80d0516fdaa9a3b792146fb78ab48be2326684c9d98ec890b8e50776711a9de88a13d57bf3bad540cd2bf86220d7645ab9118dc1ab44c39838bf86647418746d9ccce36b622204e50eaa76a82edf346951503cd5d5e8b3c66614bc6f62ff3df8862215e9e2c264d1ed7bd160c55190b9b3332053241ea8a3dc0d38cee41ffbd39c6517a9a2391767eee87b34dfa6223a6662451ab28ee7c32d6926328adf37e3697f41ce28e9b315fcd10847f6571405be820133698da0436572c40dbba0c25060f50da124d38209d9e00a8b3fa0a7cab9a58ef546936e0861fe49bc411ff0fcbc9885c15ba7448ac23f2980eb808ed78b1bff52a289c1ab93d9fb799430da8f94020888fed6f334f715ac8f0ce47427126fc20aea4db35e392c319707d19a5164cdbded34ab91a4a522dd0d8ae1baed6deee3d130605b6ba2bb73bf3bc0adc72daf63b7444ca5af52ab84372b5578581d116fd021efbb2bfe54c37bf1be2c764b1d2ae9820655ba84b405d93496b8cec558120b8c6170013ca81fe1021f777cad2ee99743e579ce8773d54738a57dc40e5ae1fd4a4e9077c013df0c4578c551505255798725cd66e32d16aee8c543ce750d6cc8f947927b177b1ff955e910e512a14a81611c83e8e0969fd74726914f632ea655f962c6294ad4259a50519fb1c558adb4bb5b9295864924ce76b9b13a24442d514d0e7622e88b18c94150b354167fa30095a562df5aa9a63baaf3c90972738c11b58d14c7c30dc1b3e9d86bfb7e8dc71ced9d17ddddfdd5dec4c41576c93e7eb43780c50acd5fca9758da7bd648c6eb3a899112939584ee8d0e600dbf0b5e4bc659f6c0ba42d14c9d5341582d112e3e825d929abfcb9050bbe7e2415d4ecd43b24e8a478a76d09062414d29898f2411e7d79812e4498acc76691ea62055d57ececbbb8a1078615b0c111b8d161ca558ce93c9a60bcccb8b8a0608168958f0a204653623e6d81baf923e5e0cf72770d9442e3f9552b3036a103f157003e69faeacd747f260bd24e2174a5b432be054803d3e0c7d0c07034a6c549d9362b6e4ff5dc00c5e810d40af7c01de7c1c47ed9da20405da64312002caa3799a1feabf18933879e0c394f5cf3c3af956b077b6c8b52fdf526478df2d35e802c0ea5068c1b3115657628c6b04cb1a3e46fe036ce0a7ff55c4b962fbd007a9a87578b7ea6ff9eca39149bb19c93a064e38bc7832c93e198282d2ecfcd649e434feee4cf18d15f4407ff1a1694984c1d16964168a5b548a5c9d789b93d61e5074d013420908e20ca0453a219834806aac6f650caae38f59249567f9343c2a5589dd8886decf49d7e0202887b27ba77481aeb06bc5389f8f603f7fb36690309845303d719666e12ef56d99b3343871d46a1e3bfbf735cb3896b09ffbaea233df64067df709d355dc665027a8bc7e70d820543a1b2996c61d24c0be2cc72031351fc80e6f36691c0cd93571feb4afae752ca80d28a90cb30c6430fe0b67123a91545e48f11e52969c774794bda8981f1e70a5674643e9784561891023dbf66e0cdd4234a343c7bd7052ee8c345568a483697b9e24a94f4af85d8007dba5238c704461ef9212906d2da2d3a8fb782a7a5b6cc5f95a8e789493d0a7802db34a8f64d67ebad1b9a8091bcb9440b8ea7e7dc8726e2c59104dcae96cd7431af28c90219b510c4b6a5319aba2252cc3e535c300a782575231cbdc30a1dfde6644e2fc02a4d1721ca8af22f5e5fad04d3247a1dc69ad702a57d8a1dfb04f16eb1045d610177baae8f041eb6e72ee24a3dcd4c35b1a7bd11a36beee76a27f8bafabeb0bd8c0fe45278b596fb06fe7ba6b2d929ea40812522d5a1245820dc247adde667f60bc37c91abfeae88dc5e5e1f8cc6786a2c57fec4570326d8c3be78d57162db8471076bea45b2882fc653d6df72aa658497535ab5f94df0794b2ee5c5a4db87acb2b4457e768ad80e316f4994b313e421c5682a110826e9c9a22ebdf46977dba86c1b85aec2a150fc99423e8f79caf30bd68b02c18d61d3eb5870ca47b0be348f4fd1964e503b7037031f179aa5fb9a9b8fc6803fcaf20afa9fab8522ace75e2b5952a74fe9c7d6ca5e9d54b293291441fcf4879e86951906d6a18ba68ccd4538bb4aa2c11d5fea27e32bc6006330e5c408a6ff6ee53dee40c76c88b500df529012c326763088af42f4f5ac57398b3915082cf34c4d0284993cea49495c7ad07728108d9c70aaf52d62a11a699a2ec38652213e2c2b3fd4d8dc4a2cf536adffa9cfd10e69ec6ffd369d1da17460b45d0a61c43f9bdacc8b161beb4c09b5b032d2bf68394946850edb19444b3d6b8ef132983810e8053cb967adc0b285b8a95adb610c8fde24e47d67c909e80085bac1000611bae24e7fe67b3e8f524a8e53a2117ac5642a066c1250b6af190d2a0e12889dd6f364afc49247b02fb18da54050ee450cb40207dd8c6cb11980fe82583820877a2800cf70b36928267f8b1124055ac3c00c994f3af9e163922780c60a584ee948dcd25c0a46948a38b780c31f03aa56333ed2465bc498ffe3ff534af74f8057ebe59c3b85cfce68b94ca21af9d51616144655f247d181a94720003145ab6cebd891a0592d4e7895884d18959c3c1f833024bbcd6189e29b1857b0b59b1072efa08548ba671923f6f6b235656f6f739abcd7795a77eb258943dd2c2379991285f1818c46517a816a9eb10717eb5484afada0711bfc02c7229f6eec6d8387dcbbc2f6d29b32d56255c71a38c4d54d1b409a3903991228fdd7815d917c9726060eb066f46e25e3af89ea9215cb1778bdfb0ec716ee0c9818fa884cbba2d8c4c7f5dc7171212b3ee8da1161c91080c014e67bf417fbb379fe4fb9ad2458c2e6533054c48d8eac89484187100289f402f93513a18c3167b310963aacedb83efc0dde0700b4a36f6815e23c05a32c42d047a8f33ab54305e19a503f808a2d062e0f54f717eb72298b102d7294de80620ba28c3fe4e40834b918e06329687682eefeea600c3f0a3ba04b4864b6712db3b9a525a9c9e357231f83294b5cf66c9267b4bb9b794522699920c6a0a750ab40a34b08dc91447ec1449537fb3f1e54529a53897fa4751caec7f5d1c9fb33ba3eda354bc6e477f6558f95786ed183118067d2abaaeeba22fca70ac6fd72512d1abfa7096e56d1acb2e111687dffa17fd5d79ac5e19968eaa5bf9171b6157fdd196a55e10e4dc150cab5744dfdfca62300c5a1bd74bd5180c4356128cc130487de25763300caf14636885d9edd039e75c0e900863ec980324c648a974532006a2e9508c910a758cb15482d547d1e59a7a17f34acbe99c3fb169cda773524ae7e8e7b77a31966ed28b26cb8b9e246ad1f7b3ac88eabd6a8f92a51e76adb2b6aca286d020121535f5d3ed0ebea6280a3a98b5acd98ed175ebd6addb2184cecdb0b75d5314d6cbf22f9df547bb84240a95304511e7a673530cd982d016a0d818b631d9420f4541ca51f0ddf970967ad68d1c2b7ffac7ec62360748142a3e45b9f70ce7391bf963f4f83ea9eac35deadb378aa228f9d4d329b29eb6850327562227faeb125dd785bd88e53b5ed48ebef5cab3ac8c30516d995567ad530a898e5a66d163db275076b11c206d46f169c5049a13287eb5e364fafa2ba9ef1d57c33a4ea65ffe55772ea5b48a7a6c3febb89a956f1ddbf2316ab1d1e8a299ac559c3ad47c5aa959e9502a654a9952a6941eaca34ef44b85ec750c7f3253dfa46ceee553207ed99ca5e437dbb24ba958d72c948f0afd68eb54c83a29a50baba6799c4c7c497fbefbe671f1256d1e1bbfdba7fbf6dff6b151f471f454bd18f6d48f9aab597959472f7fa55e49fd28938f65b24219aa56591bfdfe2b13401198557bb42c6cec21b3de1e258753557feaf610ebfebd7b1ac440c8f83042af34886138e7637584620b3b520cd929aacd65484185f5c1362652f848d1630d601b132856d8d9a6b70a7380789b69efcec20cc70710ce6efb81f5f2fbd6fe72ad5786ed87d60dbc81df2c38eb75011481c1faa32dbc3938ae5255fef5ef8fc140c4fa9ed6cbef6a0c86d193bd499326cee33b4e6408e8876358a924a51fd1bee347384f13df7122ed45fc081edbfc5c1b087a12bee344bc88f3f811cef3c3815880fc08df7122cee33ced1aa8891fe1473811df8912c88fe81fb27e38901fe14730901fe147fc7020dff1239cc78fb042e4da8a6ea251f1a10d8a7d41b40c7313b6aab79d0bb34f2fe926aca87e4a33662e6f399703a4629f56527593e85a0be7985bc416a26872d9ed262c3b572f73c5b95ef45595030476131ce7b4aad84749ec9f8ab6e825b4bb09cb4fff555dfa2b4b5fd116c7dfd56555bd8cc47235b9786bc5399fa6a9bebf957413b79bb0d5bb4491cb5fd5dbb89a215ebef58be961967ce5fefe700ad355d48e7fb80543695be192f512be3206f49256e2f2b14546df6570be0bb5ef5fb24b5dd8c7ea255c2ff958bc7a49576e457f7f5c0f2b4ef55144e95531ad90f41246420d1144d5a0d8c8993b6524963e6cad7b492ff9e1487a897713360be825544a5c8ac215d1fd0a913fc13f16b6582377f77ae3cacb7a7fd418e57bd3441ecee489a6e19b98b975c373f69bb2d1c7ac9f662ff25bb8d00126fe0edfd60d51bd98855c6fc9eb7d1bebf5c142f71c20a2e82ff227913e11d6493f73acc8bdff3817912a8e7b6a8ad7ffba8907cfae3f61e30e15a6de9f830ef596da30e3df9abbd7dbe307917b7417551ce79e5dcc7ae700218204d6dbfaf527f6f6ff681c7f226c641efca2eb4da43f7fd7eb4fd8fe53d66264e5b37cf7d2559c6f5370114fe6d0a40dedc7c2bfed0c56ceb062a8997132f06fabc2ca6f8d86e9f6f841ca17790b170ea03f9c09e6fbe5f3cedce1a90bcbbe94c33f7e8ccfa369e4f3fb0f03115ff4364e747ff4f527acfc17918523befc254b9fd5eb03f65f7980d4efa043751da4f74ae63ffc71441edfeb05418e33895f8a8fc5bfe253b1ea507fce2b5e3286c99808f3f4093e0d16bda3ffd848bf337f1b67ca46ef2263f916d9ca9332d2bf60d9955199571e7d9a812f457f4bfdd7077b676cfbf632662dcf160e37fae66a8668b93efc9744f596a04b669a4fbfd9dbe387cb2dcc14ff6d61c2404491429815a52bee67612f1761b9c61fac553dbfa8562f5fc42211bb882a7fcb8b6a4b35cd8f4ffa211cf4c187ff5209ca8f3f58d1df1e3f58ea3192e859b2951a7f84435da2e71d57c34da6d1b476ba2725643e2d8c8c7a2ca47eb0f23167c24088e45f6f3d0f6702fd09dec998489fe053f5328f85505667c23022ac380cc33bcca1998a421d793221f3f5cc511eb1b12b1471375e823ad2fde47a287a9ac5f780ab69f146cb0102ab469d933d9411ca07d28a5a02d2caa57a17c8cc95f51072f5fcbaa9a694d84b35744bbaa78c503ed0fa764fc1e76f4d1db1d4b756338226f31e02adc6034e884683f4b1b2b87e8a3f46e866530e52c5fee9289accfbc7dc431969e7ee8fbed4111b5f9459385af4575c713e2e917a69eaa2e8874093e9aef0299f8e75d617ea5daaf39954146a897ed2509a8a3ebd7e423d650487e8aba732be9e66160b071165adc703aea67f46d06a9a14c4beee1f57e37e3a8a56e3a28882510c63961f9f5aa20159f7b74703a2a88a232996235925632f9251eced273fae8683b8285a0dbba0c9bccb40b49fb81a0f4420cb2f7af893fe954908d15e58a27ab10b5aa38283b81a083c6940363e907def016a89f857a596983189f7b2a7699698933a52c5ac9f34947ed24fa82518066cd18408bb2612855e268d4f238b23b916277ad75d1b0d97bf6e65f49873e19a0707b150d3b475d3eeca5c7c2bfd9cd27d741f79340d5094cee41ccb218e26f90c00e74c3ba449c8b22bb97ad9b173ce39e75c741c7380346ca682e3dc026220fa250fb9fecb42b259c8c2f8d1aa381d638ccfb22a8c3146f7c3c98c86e96f84f90bad0e7fdf0e3c7c371affeeb9d1c868d477fcc39962fe03e05c3f03b5942c2459c8c68b6300be85eb213a179f3a177b084013e120860163de5bcf550e6220eb2e0064f46bb6f25936facfb0e72173f91d32d1eb90b53c4c467a53c6f2eecae6bb70c1e3b5a82fccf54552cf2fdf5a8d0fae5f38a86513b272be94ced28ad31f5f5a2cad96596d353e74e52086f1e22cf52fdf0fd9392b5d5a9ee52741cbcf92b5f662256bed05ff28e3f6821fcbbabde01765de5ef4c7c791d5baea6d513888818856e5201e1c44290bcdce3e463e0ef2216b31af768972133c6446347380bc79cdf9e6c54358c83567bd3cc4e7cf36b38b7578c8756a290bc13e46c84278486cecdb1523eb44e6b9321c3aeabe9e752edeb9e2a8e25c4f57580803111f76935648432cacb2c5ebe9d5b4b290cbb4f2ae0663794b8787f0103b1fa3d853cac2cdf2d72dfdeabd99c5e226b08c857ccc959d9e85b00e36c258c89c95e78565cdf295224959f497a89a28ccd5bc8b2c24bebbde2fc84258080be1211ff3cf4246bcb2b3d2359cd428d20264df4ffa357830513f2c9524d71a38b0764cdb168e15d87050a348d35837b0b26eb8a92765a28719f5335b7996ac7a081f8f93810f27848de190f11fcfac3c5d6fc9de39dd9c3578660d232ec3b11acb9ccf5c83a767b2d4c081eb6ea951c4c2efaed0ea86ddddeead6f5851394028f85cc388bdd4c3ac6776dd728d9e0a36cb7cca718d2296f2aaaaaaca551cf87d7bc892aaaaaa1eb294dd5a4df5c4c944075453cb535f61462e1717ff02e1c3e7ef1a3c0c047cae356e8d22b6fa95ccc2b162bd68a58a317e74516fa9458cf0a9fef0dffb67e9b07484c056d67f46ea5949076571bc62925482793d483a0c80849329cdf26acaf15e39406a2298e86ffdafbfddf8b773cc3cf0ddfb74f35eedeaffde7bef357ccf7c1061df63fb685ac67da394dae7fe322a07ecfd751cce49ec0bd84d3baf3f5c8702aa54a9f2effb5f15815b1cb742e802cf9549d8db83f678cc2ca9237df2964e1d89ef5d6d0c03be6794112b7b9c5a8261f84ffffcfc2c51c23e4b7aa7775aa765e0549c8a53712b7cc8d1e0c46c0f23358d1b1279888cdd2cb7738e2dafcd3316e23c7233e99d9681483a9955effaa7cc6e641575d9bc321ccd5e3f1d950f70ffab5eef7fd53b6dba59573531a0d9eb71347b5553f538d7c3afaa00548099725c0fffaab24f39d6a6d9caaab771fc3996ca7a94acac2d677247f9d119b2a333812610f5f353bd833e4e2673eb52ca8cd2a6941e234958477f7ca4cf18a69429650c3fb1a1109888b7371250a13e452ae40efdf97bddd3dd975c0d7d5841a9c4c9b877199f9dc17f19c3e87f19ac97f93b63dee1dec7bad213eb9e95b89a52cbb82f29b1ee7d0ad6180da24153b0fee6d23da11a7261b69340469f38da27a34fec23bef7b1ee68311806d7981f9c0f61fda5ae01e28120418204b1d182b711830dc0ef9e04a43afe1b48c4cde1acf5e90d61186c72e609ad51be05a65ec0ee6701c3a1fed9fb967ed3c469d1c79915bf5974523506a904432ac1b42df89b410506ff92603c0b18db8f39fd3023a369bcfa180c63d6f9353ee687180c8363dc57a3743b591ed1a3c3cc3a5c9b4bc133f28b825de6612891a2f3bdf8919f8d7b3d4918e949c248f391c2df9b428f0b187cee9e248c1cf131028587a74812468ef84c2913e83d9e9e223c459298526694d898e7d228a58b18f348ca3d534a0f18668bb9eec7ba672297796c2c626365f97336652bd6e653c4f93e56f62a306735ab050c47fe9b8fa2936346dfbf6fd68c14bff86e65a5418e63ed9fac9fe826fdd34f34691ad83c4ed1ac473a64f97db82a55bc0b187cee9fe96837699acbae058cf9df47244d23957013fcc3f6d2208691c3440b3ad735c1469aa631d14210eb9e8f3012da2c4981d8773cebc355a942c45e36e2e227bad37141c5fab77f4ab7c446a28c551aa1410cc3dd0fe586268634011fc2772d0a0b81cf63088ff71e8fa611c2431c7c3cdc674a8cfe1afda4f1a708bef7de73cf83752a0fa99eff5dfaaf5e1ee257befbf651f4d67cbed7bf77ce5ab112d5dba2b8776b551c59ad874e424b561cf7cf5a95855c55baf7fcc69458367a08a91b652b2f33d8ac1bd6db34cdd8ae64de32147b2b9bd44a19f993e9d30ccb64cb60a4120c112ec76a0b0ed3bd26fe84b3b0d6b06e867555469f1c6d973eac578675f323fc86fdf0636beffe6e67c56933fea535d6ebcfba8f417fd28ae3ffecac31bafe687b5d460c8601e1ab6c1acec8808eba97c16f32a0bff7fc41e71c7ce7e0730e66b25e6f3d2b7d9b86f1a5cca88c2fe7d36cc29b59513202a9ee3277732cf4e7ee722a9e5a509264a424c968341a2161252b2bf5f6a0499228e1a1992bc3c224489228191a25513242c24958090f7192c6496243e2c4c66f489ad8f80d09921f1bbf2169c2c66f4894f4295e5662efcaf3a5c449af7a517bebcdb0f03da9de190be1fbd1a85eb7573e7c160114815575c6ba2befbd57d1e856cfc6e7240c045671aa17e57856543909c3e0ea554ea2c4424ee25e5d24aafcd945e016879f7aeaa3122c31c2c73a6631c6883d24c2c2c882e3bde8a757ef132b41fa973552952d83510b7b9b9ea20ccbbc4fd5b364b265aaeafbb164a4e7ac12bd27b1485209e6297ca8248992249d83f5480c3dfe8c7812aea78b381eeb884cebdf43ea8894fee5afc44ad4e85a81a5fa52d52be535c4b764adc98cbe610f474f1a3dcbe8faf72bf1c7b3f2bd77b544efa938923fa2f547df91d05571462f471527feb3f2d53a1a413a1a8d462347e4492c2b4f1079c206468ece08b7e38c70449e18392346ce0847e40967c468d4ac11352deffe5d4d4b1dc2c9b86ff951bd259787f5a592ea952eb53919f82cb5a5aefc7c916359796af4d5bf7a63ec1011e2b8d8b6eaaf21dcb3d42b4b31f679cbbca792641f8c7d6ffe133d965dcf41dc951773a21765d7e98797cdc87035b265f831e9883823569ab464ba8f2f0bc7c25dd154acb715d6bdf690a4e450ab59f92b5dfe962cac97c22749d2cb6fc15a5ebe4bd69a8cac0d7e4b46caa85fc95a938155004560b4fe78d6fffe78d123f64f561cecfd29cca9e94f451f9ae2de433de478da9555c9ce7032ee63bd3f5e4f1162205a4fe9a19ed2432dd4403da5871c4f4f69a0290cc39f044ec6bdaceac5ee0ceb7e62a51286f1308f93918d06dfb7d1d5fb96c25e7b8d8a4f0277094005d85beae3a8de5249545fbdd855af949622817c39b1b00e3a086f54cf56a4242525f5b162ed05cc46d65de62995fe4fd6cd58fe3b635d0ff514fe7939a25e19120a120a120a120a120ae20124d4a2dc06c4127900f10812e21124c40388479050108fa039ca58de9aa20c56d925c187855fe1a8b4156ce1908dcf83812a0bf529be876fd57a59e806db0ad62bbf7d6b35a3bfd747f85413b290053e7cf7c3599959ef0f8765ad65669df3af14401198ac3da895f3392bf9ddca8ad3f19b3f0751cfc6277aecf5c18a926cfc8a7b580f34185b91ecedc124eb15a7aa28f557a6c4cacacf7a49363e96ad3cccac87580446cff2ceba8931be8d9b908a32f8518995b7e919a995c1af20307a96c7b2951765d6cf2a8bcf16aca3487db370505f55d925d9ea59322b937daa7e25a32d533d5b37a8af7e3265c946bf9215c1041332be12d8c7ec669479cbf05b192dc17c8c123a6f03ab58cbb0509050905090509050d08f964ca6333276788c99183262ccc49011c3fdf42c60cf624d23c3d2f8c7f7695d4f65f2e78cd55938e87543c66fd60d5ad95a19955d195b37e4533fb971bbcb7ada24c5487d621931285347729c0b466c0bdb987061889d8ebe1e2fe2789a8875b7b1ee58af3ce4cdfbea8f7ed60db6d6c7f6323e15a997a9eaf548d197adc977db2aceac4dca7afd59a72acefb96e3551cf8cfb6ae9e851de337ac4fced56c62feccb0235b583940ad571cbec1300d6b625ac6b1901d35d45f6a634ccbb81bae598c9187340dfc266c9c5b37cc42e090e94dc33a4278c88e69ad1bb769696f4ccc080b9873ce954ad601a936ee0d11c2303ce745587d38fb5efba9faa20195ef512078e50091d56575f5d75320eb9292ba4bbe6549f8de494b5279c96faead2ed1201a4483681014cde57bef399c1bdf9f08cbefdbe5f7324247555f5129a59452caea7f34cefc7649497f46a964f5e1ae39bf256d5a45f8bead7692fef55646ab83e0ad6fb569f5b4befb9951b25edb62dae2b804f5a6bb7b3b7cf74440f7e93f6dfa7a2d299a0324ce9f693f3855b462a43f4361443cfd2b7aaade1fd76aac5e10e4fc95b53e59f5d2faa3a9efafb2180c8392b15e2be23707510e9047a93c3a4485a8100da241a31c20ad43812895918f9e028de8d0685e2ad443443d64f4d7e988326c527f1f66e768cec74a348806d1201a4483240f4625f573ce1c20f1452b706ef5b055ee2fca8f0f7a453d29f1e714bdcfd9667c5186737db3ec237791bf288e2ed1579463ac73bb082b1a89aa63a3d19b8f61d57bd0e8f3df5f994567bd1525618cd99402eb94927371cc220b2b00db9850f1c452f16339d64fa65635e5a02feda5f4af7a85b056651c79e1533915c338f83be79c7bce39e79e0ffeb1fcce55f2537eef4c8fdb8148f76d92fca6c86f82dc6d5fe69cbf7b771eb4bffbbbfc6692cd4efd8d7fe1dfcc44f59ba209f69bbc5972cbd8b28fac71bff75e95efda0e586545b517eedd3f21acbffd95ef2f330e6febeccd5d7702f6bd6f4dc37fa56cf95d258d4603beac57f64f297380b5953ad8e2c80fd85811c00fe40471e884c8c56f2f0c18bf6c62b01f78ab79bb75e33893ff046b7fe563e76eed78ef2fdf2b25849572c7634b007b5580c5674980283431d858618d695732ab9fb469e6578f150023596f0294c5748ace39216d4df36cf5cebaa948d6fa69b22c0562adbff7fa363593d71b95e5f6c2bde9aa37dc5fb7f05db336ceca7c0081f227cbf9cfd2117fd62b7a6fed85f5adbdb87e66a27af9ce7f0f65d3bc7f7b2bbbf3ef7cea29ebc6fc7e5ae1c86ad211df3f567f7f985dceb1f809c13560577e13db4eb4008aed8ec28bd846c18d4fdbdd12001f086b8fb91af8fd5202187c2a600d8a2243f6f27080e7dc00f86773cdcdaf06c239ce3594d9d2861ea0b44c01024b102a2f9c94e8210bb7fddcd64486bd27cbff6e5d7b2166b81ab7d11e6b1a0358fec62ff94bfcd2368b9f980d0458fbc9c2891dd9f623072a6cfbb19207cbdc9e5b4da9c9f0b737b51a9b96e1a7fed6684d6c7362db0be16aa46bed17d034ee5b9f2e006ee08fb280dd92e512ccc7344ded31c33e2380891e211d3c618a2bdc50829d335039b204305049821620172043a14414059512cb504541a594be0463e3621f8147804cfa6dfac4801f1c0be8133f3f5feefef605dbd37614b6bbfb674a7de226a365f86136f33025378282ddf663f96d16d0346df9191044d3dcf6e3f6c6d89e61fbdb605b86ed2ae460fb65ff589bb6e952d7481bfcdf35ed87bff50b016bb631a9e28835d9c6a48a2e58681b932aa8606fc9b627b22b28cc2705d6bec53927c3dcb46f4eba75e3ef4118a3941445393b725319a9e6a4d46a587bd138f34b7a17b1ed5b8f6ddf78da8b8b7246e486c7b6175938dcbe9df6a2fd1451ce30eba68d1af3af28c15cb5e9365ab16e562ce798857246b26e943461dbb7583748ac8b0bbbbccb94035afeb65c2f059e8f6d2c24524b8b8b75d3faf4d2f2f2f2426259797919612f2f2fa297cb7a79a1b3a2647c7981cf5d73f3e1e2bf3163b95671983525cb9524038cf6a925148c9deee036a254c2c03f764b509cc01dc37048247bb12323905204edaeb4adde0e58f72f076671aeb46c833fc7998d6036b56fd674836d5bb658e36776156b9be35804aee635991158f7aed97819db68d60ee99a5f703a817b05dc1b03c140308cebbe5b070a6c5bcb3bdae2b87681f3e6da7b6bad55ef16b8b77bbfe3f8f3e71ee5bd0b76ce9d3be79c73ce3d18deb3bb51039640d2a7662fff7b5833ec3ded9393fae4589ffc52eba5126d8c04afc7a1fb73ec1435763737bc6236c7390464c02cbc1042c8af0bb6f5a0897f1f95b5f75e7edb1b33afccf1a38ddf0db3fce6abecfb7eef7d6a99f520d9c75da01ebe6c35f263cc1a26b2389cc96c4e86cf0f6b4b12b0ebd6bde62a7e1ffffb7e9c57659f9afa426d34ef2f7feb130f52301c7e6f3fdc3219a4c4b0190590c95a946a587f890d9114239148561c48926235463a4ff8a08613b06143c9844718e1eb9682be9b61082c60a0f40814232d29839bc3132023744b119e40d96850ff9e6a34aa7fdf604c38614422da5630424b45f8e27b104688cda0074e2282ef50ecbd962ad2d217d603605d07eb584bab1182d468c02741114290c0d5f0c083946d61fda1bd700f4b2f2846725f83bf57399c7105d5c3b651e4c3c2776ace3bed7b0f818537d47f55dbbb082c7ccf51397ccd9b9004a6ea81922aaaf0c2a4e4ccc5acbf272c1365cdf598895760f2af5b27ad8cb658611eb0f119b3f1298a87999625a45842ce58af557b0f60aa2490f620b214a10c10c658a593f207dd469630f3e20425a4222871862a1ae1794565474a2b96fe347367c029a10c824949fa1356ef694684cdb8ea9d315a9955bd2f84ab2155965269a5d621a4a4a1e2108fca9ebc955004ad57dfb9fac1c18350ec3dc6a209596f43d66d0382407313ca54acf310033115d69f9fb826aea67aafad84b110d23b8ec25cec1dd83bdcc4de522b8180b35155067232ee454828f110c5488c85ef30d1612c3cc863c113c2638333bb940723739352ba8184549c8f7577db5ddd076bb1ee884bc2157144dc10ebae08ebdf59748dfb692caac3c2c536b89fe97efe74b2d7fdccd8eb7e6c63612f86858b85d5e9b02ce186389f2e0212e703a94023107209831042f8de7a940efb6115c1b3dea7b7862ebc1dea8a35b83869542ce145a8c4b3035f59027b4163e11e62d7dffd61bdef3d1caf181446701b3cd9683c1d6d636ded05e7c808dbb16086b27583f5bf9365e607212451acd1785f5a41c9d5c43424f529c60698f729c6f6c9d957bdbd70eb27b0afb5537c0d32bf210c416708987fb324b9c14f692486d20a5e637f6666e6667e8f04f3de37f93ecaf7cdbabfe7ef555abda578afab934a30dced48bcf79e6c01f6228c5404e260842fbe577f380b2174d69d0c5cc6b52d815435219cd55343587ff6870fbe079f6322adc8c7ba9f9492484fa7d1c0f7eb46b0feee737b50920b28468a7f5b58e73e8d8a75efa875b363f9e78a0fb642048cebd580fb587ece2889c508c90bd87d3ddc970fae624d399cd580a59eb4176e87ebd8fb7a32fbea45054b0fc67f2b23d619c1deca08c548bd13c51856b0635de4a306521440a712122d8b49229560bff760c348654cb8fc17dfbbe8ee50a475d3ef482598eef604140e85f8902da9c42fdd87fb60120c9348303014098654c29a06def7fe54c684bfb4ac1c2f5a37f2a115448e7599c32bceab9894d414a4120c77c37e3c78026484dbd400f4822fa8c1b630015449b1841440fc24073f2b4bb88d0c8d0ac982364523c30b4fa3626261c90d1a153f8f87b0b095302af358058bb159d87bf5beaf7e62d1d2e12f2bb47460c07e4549eac7e3a14a952a56be6d96aca6fe5845f02cac38af6255ceb82a151577f7fb1e0cde8b2c3e3a9e0f3b010a4e783e15959e35c4c09a36c9f695cddcfc6aed857f0f927db04acca5c430ac51dc24c55a1389ec3d3d0b2ba9bd70dfe203d207d84a8a7177185a76aa1febbfa204f3ef5b61a1fa896ea89e506161868484eaa781c18916543f2d3b2e0bd1c54a55d3fa781dc4441fbf1b8deb639f68fc6f278865a2eccababd786f65de5ebca7f37df59e8a39dc3a29a3f5870fc6f8a8eb740feb58ff91d065245827ce815dc048ccb8048175a21360a0c23ad6bbe706940af4b1fe2b4930bf100ad6a18f4c012c82857f211460ecab947c80edc524853edcf3c408f4a1549c0e804e2f06c67a35e3f48619947cfb6690a220e9a9859329483839c91e855c2102e66fe3faad97accbf95786f5ffa16b18c063c6130ce0e16adc6c22ab7a2556ba8d0aeb2cac3348516acc5849420a46c8199683b814fefefca13f7ff8fcfde5f05aa50a23f1e6246a1d9e1c0bccfde46475a7d110c93a07c8e75e1280144b4851b2de2725d15ffc9751ef37107bcf41da64decf2a6b3bb3d94eef453eace77856b29d8f5bbbabaa1555ef8cf586c0facbca8ac0c166e5a8ac3b8cf13de8ef30f8d7b8df73e68308eb2df3c1b2625262587c979e615960ee486fc0fcf92fc9b627d6abb7454981b1a4186d522c21c512de6e03d2c37f4d7cd0f348a42bbf6bdedf93605680f95f9275afc2bc93d4bae12cac520c18cf384bebf506d64debd395360d238d19ccb38ffbf1a1fead01b3a546e372b36e1e83c9da4e1b3d7cb33e70b3f220607ad1d6340e731455992a49cdf9524a2ac2222a2bfe2512322d242c8ab981629919afb86192660bfd7f9ba678e5dbba51a287af6e5858be5937d95fd90de96306d93047b4878cb17032f0f989cb439707009599123dfcece149cbc087f0e1bf29d930e513cb549402cbdb3465ddacfc9c2bb639118320d84b0900b060d8dbb485848bb769919016900d18b4cd891814611bdbf93466d6bf5838dac25859c064730a98b7e9f912c494b1fdcb9a2d68c5ac1b12c502b32800dc3627863821bd4d4f1734cbe2f3b062ddb0c038acc444294c171a33d157d64ae52bfa045f94fda00560649b133150123375a000c8d84600321e7232f063b2e6645ebe590cd4271960598b7583ad1200f8a903fd2cfe0ed98b75835506ea79993ed098413760d23626572861af8c2dd5038dd96807eb868132b6a38c857880c50780c9bac1d6c5cf1d7cd02263eb830e960fb62d7ef24063167b80c58f1f4b19db9315eb5303c631960fb6a59fadba9a05ed05fc6f79468e18b1f0e70e34c222da0bf82d6bc078a84ff0e1ef60dd68cb402cd427f88f05ace5f9e508f624d1c3d68d6ae7cf2aa3b266a5751379d8e1a9df81871daa29c77cea67a52aea7aeaf99d0ecfd6e930afbfae1a001a65b46ee2941917e913fc0cdaeb679c3f7ba055467d55796bcecd0a93b53565cdbac89a6dd1ec0b7c0064cd864bf60187df925d4849cdae64cd625fc92a635b6a342e11b5e65bd6ad5e9aaa69ca8155d5e84d2391754d1f40ac893ef5d4acb0aa9a72544fd9748561b4cae877114d439f9fc5ba29351aa5f6e2a5df7472521425fa6bfdbdfe563f45d65545a65a502b7b69c09a1342aeb0b76760e18d26075b7941324b279f7a4a66d4cf2679ce7a4760abf854f5da3b3af48d3578fbd8fed9f2f2e55fbebdbc54fe71d969c95cfee5a7b36e5e9c89d4604d73f9c752d134cd56b0fdfca4691a133010b1fd0ca56932b61cc5f633943edd583f62717979a497ccc5a5c5c55d5cfea5e55f5c5c5cded4e2e22ffe2dff92b1e5d1270bf4a93f0821624e367d5a409fbaa50c1809a3d2a66948dfbf80a661f97e0634cdcaf707d134a3ef17a269b0ef9740d388be9f47d35cdf6f81a6b1be7f085753fafe1fba6603f4fb4bae86833899fe2bc3f2f4a98b6cc0d5b011c73f5e2f3664bbe5bbe5fbe5d9b6a5e3c5bed43b0297b7712e190fe9534be59dca444859ebf4a99f25eb217dea5fc97e9435917e51ffd56f659d449ffa4b591b7132fd34632a195b9131948ca9e028465c0d5fd1fce36a18a88be02c6c1be987fc63fb557e5205ebefe7214db3c344ba06ebe7241a0d0ed2efede3d2d4889de6240806a29fffe46a82684e4eeed49c9c5c75b2621b145420d98161f0372741748d0f1218a2070980864e1cc47bbe8083e6043ef94213f636212a57d8dbb2e0224e4d737fa669ee0e969fa94062572c57e1e49c733b3010cd4a4088209a13176648831165584111234fb08115a1640a54e818018a1776e27b4d4ae73c4ef99e94523ef94fbef7de7befbdf72084104208638cf067b618987cf6c650046ef94d39b8c7f21476267abbbb4ddffe1cb85b6e2d7a89badffbbd35f58efad7b73ff0a8e783ab0421a5bcfd3fd846c6e0bf9f6da00244158ef063e365cd467f0d06822d7fc9d5d46832fc7c6bf45facd4625b142c3862bb5ecc470d1002147b6b8cb03e99746cffad619bb4a0c7baafd1355810fe050861810de8582e51692a5ec8420d5bc4eb61a231e98211f64213f6fe6d5658b671db150d188293ac014a60913540892c9a068a29ac60a5e587691aae82e52ff0587662b98a4987b3fd389cedda80213ce953b339d6a66fbc9f05a0020cb30be813dbd41a7d621f356009227d6ab686e52ad0008425082188064357d8e0390c91010a2c3ca148902136da08c307530065666666e641695edb10f6ed60be5601779052dffe87a6815f1ac280ac10ad460827d35f6a32a361edc6d8f633ad6686b61acc5119095aa67f2801e37e219eb88f0d2d1d4f0706acfb917246cec838e39ca4a6c0f8b1fee6baddb763e7ff70dcfde087858a5acd8c6c332fd4f2538c549a99218308314c814194294534a18402e89cc1064338021a7e88e854465c549899312363c6d5d0e61e1804c852a0f41443c2689b9131a3c68db13c05288210032054c1840c98b071319b5304291409c28113514882848d9c2254a444718624a8c041156cdc192ab014b0d042052a5c01064c104214a64049d48511ec3b42d9f096adf7770feb4872c02eb55d6a4ffc96a2a47d6af938b26254845edd7f0183e98a44df367ba96dd928577fb0fcb6db0a22c7bafb1e24fbaab42e33e5700ff263bdfef0510ab3b91d33574d39e9dd1fed6defcdbfabcf07e18b2e1dd515bbf7e6b8a2649c97eb6ed983f9d73976ef4eed4563777f9f1e639d8b8fd25de9fd2ec3fa24ffbabfd2653c48c1a475cf38ae4a8f42bb3c7eafb5d7321325236ccdbdb9736fddcdb52a6c7c475d0dec0ff86c9da5e3c5c27a4760a3bf7bcec555cec908dd5154455155d8f753626eddb4736ef2e7dc6584fdde73fe2e73f7eedc73d73025b4b48b9b83757730e6ba23b05e231c2053ddd745f85c4b49496965d38898d97d73decef96377e7dc39e7deb9b716e3ba23d8e19ae29785e7f720f40761f5a68a92d11d3abbd6ae88bf0cacb157117e840d61eccc545192bdd9f67bf8cd568eeaa753cf4eaf66fe3bb54c0c16b0bf31a42760fd336e67d08035dba0a721b615325af7d2f2fac08039db9878810c7652937af6a860ed9f0a338d4665ddcff822a4d7bd404b27a57c9c5d370708f04524b07b8bc4ad3d39193e0206d32777bf3fa64f0eb36ca74be7f88665d61ccef8574ac991b317845d927539dc6bd65e38ebdb0bf74f08bb24189817d61fb3e1fc5521487b37b57bdd6ca26484ee5c3be7bcfa8ec9fed839d1e87bad392923747ecdb17bded1fa9df26ece91ec86e0b798c9923e2bb0f6d3c51c5557cf62bd99b36ef8bbc64100dbfdaddd3784ef26d71a02b1ce1f6592ed4db1bde9b566eb321f406cb70f67217c6776af8372d0dd59392a6bedc03163bbfeabced089f9c43045ca3138e8e037f82efc97f39891ccf9ed67a42c68c292192bd06cd08a82f9cd864d4fb62527e39e720728b90138ec95019b8f63bee408344b02f3e118e27c8a728bc20d0258ea6d5a561f5c859ad915c2ca9f15f5183dc2e8103ab5c34a8ad1083376931a03c5c48035fb166d4d43b167ddc49c679347c8593f57f14d547c938c6feae8ceafc3a23333f5e7046936a67bd7d60d02d8f86ee5a822fce8168e99589fcbaed630df1830f71b906334c164c09d261030c01f7e031a0842c083851567bebfb769f9f1a7e89d017b2f048eeae5cfec52dfa3b2f3275b3aa2a5a62001092741b4294840820a765695a5ac1df21dacaa87d59d4f559969c28a81f8957fa3c94dcf24e19b22ac66e603888d55e4f830be4dcb8fcec6afded558d5cb94bc6c23c724668b3980a91f07f4e9b78799293ab7699a2a13e56f92ce11b633846fd36e6920870a12b4fac0c22eeb4fdf15309afdc06a404e7303b8adc91374ec0f67a18e4682e407396070c2061698a149cf02e8b7d5801ceb2fad0cb405d04e820501ac21e06189fb16f380744e6cb78244297dac69aea7367de1bf2b33417abdec5a4f992cfaa649df54d13751f46af6f90062e195f970163ecd207c43180e7dc7917ffd63b32e05b202ae628389f744543674bc082061c37db378b47032050948389982041e1bee8d01a3efde148c3e0e7d6f7f3dbddead9b8bca5e6662ea5dd7f514f534bbf36fc54fff65a649bda9a2de04a93731f5aecc07106bb17d56a859a8e4cb174c9bd829443310000000027315002030100c8784c30189a80a26f51400118caa5060461588e32887614a19638831060c011001019011daa4019cfec2b25db599508915818aebe07b478a4d07421aabe3004a299cadfecab867b531b92ec46e6e358cd3b07b68ca4f4f0fca2426b53a863107765629a9fac1e0e324b22e991696c8ae1977498a9b3e615ce2baf94cfffc9c4db5e12c98cf70f6a7a51d78fd492191e7dca543d02860a989e455c503209a40ce3623b415000c2c74a7b8ca2728392a301218a97bde0151a973a41509a0c262e624e5a32644fcb4f2b89e794357558dcc0fd346a2048325bd6102a916bf653c30bd1fbad41535e879e0bea4af6ead2d5950a52bac6894fcc87d7388535d39840fbe7584eebabcaabaebf78b051038e07ae204998cb3f73fb8c44d484eb494bca76233627113390bf27f9e82086d404422c957dbadcca081e2d66e2b2a2df4c55234584cc93e72ea922f4bd481f6ff165e5d7d10b90b5632eb71895e8ff7210b602958ee97d0523d5d542f2633f9c0ac240f7cf84582ca7f092209f83ac3a86d0914c3be4c485263edc8347348a3d81115eef44e6c7a354e3a2807214ccf1f9bf6d53c01f2f982952bb869c6d019a73739c98e5e36b9d6db87319ea4b5ede49804f2aaa391fa8dd9f53fe98fa4bb5bb5b5fd3bd90cfc10ced3a7d8a9e719876bc1f9dc1f6aa6dacdc36c596ed4f138500a6ec9967f8f54c35d490989c799471aa158acf157492119024d2287bb3474eb8c4cd47e7d6e421d0c58206b6281386c1a52b121bb6089b3781e483922de78f71d51e9a7c880e978a550fdbf71e3ea60eadd0f2d3b7408d9d27ef6680fb6748626552d054cfc784f50bb720ba69821d7e631e1aac50099fa404708dbd35047d695dd8b716e07c280f3392a0ccc6a0bfee0e7bb3cc216857898a31ac056ec370d2cfd3490f4870e42fa11bff079070f0b639d0852aa032395dbcab695d36c83345540560be43aa2a6a9965cfd01602ccbd4bd3838b152fa238207b078dbed55e7704865628592a5c40369f2c8fa359036ac0e03ebfdd311c627e9a9bb8ea265269aa1be32091b5b9428932a81ca4fcad6b0e43770b85042fdbb14a385849fe947ba12db5c689c21254efe86c1b0d0e1032ef8886d917512bf5328e08f3f6d0003ce784ab43f095753663fd77854aaa4f0a78d1c6a00922181e065cf004392799434bf0d97b3c9aa1b91c3945c20cb3d2ca116741470ac19a25cb5bdf21ac54cea988f92417efbd36bf41c559cf17e2c15f96eca61e9e504befadc4c7c71a333e0dd5da2ee40ff4529a569685e669c9051ac3d50811645f105c1749e7fc9e3abdaf82c93fdb8d80dd83975316f00464d521a7b4da487baf6b274c16b156dbc01d7276b2e2de521b3f05cc899845d5046a75e2d18cf60aa438f1e16013e19770bcf5a943c667216c8c13378afdda36cd58f23d775e4e3c0e5c307e63b625713785e3bdf53655cb8ada1f92d5411f78fd3d85964cae332a5ae88a765c625a2fd649d5d602f93fa7e71c3d3a81183a03e3a6add5049191bb0cc6a991aeef01e2ef8022249aeb9d4268477f7a001223473ea2651d2bbfb79371304f8c061974c7168b3634494741ede3fc9c9849234b52c11d8fe2e5f590bbbac0c343279fb3e0e15482ffccc6980dcd1906c00ea8bdf051ff84798cedb4ac8950499f51ff908901774204603dff911877ba1177f57fc73748ddf23df045a70a0c2e7252e92eea42b8963e52cba41e7db85a33c9ff33ae3c0fef0ab5d9e4102d3d9cd8bb8346bb51beda3c7eaeca540fe0dbc533bd0ffa7cd65d4aaa4a74a99a94a95eb1b24692afb945fa9b8ca25ade7d984e480d3291cad2405bbc28dca050d47a34a921c2956cc36bc0647e954f22352bb1d4799b25ee1e547242e560b4781a3f4d8e169043b8ab2ce5d0b38ef122f34e64997b7ac484fc922507a82ff42c156d44ab7bffc71183821192fd7a2ff79a5180ee65bf5d18ea33bd5141c5a7f8064ce2f6e8f5a14b560dbd711873d2010170945bf2bfe9fe1561ae4d9e6e53df1c472539e65317f72a84358328d846826644241dc4b46aa68b42f00910d905f7ae0ef195f34ec13636258b264e618b8d0e0b58f00f686af4ea8af0e1248159035592625dd1a3805f429788b32f412aa35b90c2caae9a2687a4652da9f3a4e4217b93db098a919c28f9f658b8595f210e32b7383fe7ed0b13e5e4e4e8c3ec2c14165c2be524695244bfef1fab58304115972093d2ef13191c60fe00a200172c5c50b3ec6585efdaa8552dff6094582ad1983e1c3f1dde885cf1dc8a87c5c86c4dfc60219e2372c7528832d48d728223645a97336a0429215754cea391603f04205d3e3074914864c1758ead8eedf2c76f4fb7e8b913ac7d1451e9523f5abda93246e3f6edea7460249313869536061ac32c6362e0fc0205ab5900f1fa58f91b7cc9a190d3f770e33d0d3dc212c33cbd6709d539d10578f39e9ae609a8043a9c041bbf82c7635502c03432d393c318beefc423639f53d977f275c519a0e55b714923202d2f047d048b0ab803fc22704eeffbe21ae396d6d5ca1de9cbcb135df728338ab2b35303e8aafb5e26a1a8ea99c00e3d3bd22c10c8600907c39bd9c4d443809c13a875dff54f859999aeceb0c753769e285062d876d124390a34af2b86e782e0d7d8175916c91a87d3948d68d6dbaf7544242aa29447b0d0aeb138cb78c8ff28ac4bc9b420d01e8104ac5775474b7da3b4a6fb3d064ec4569f6b09f8bd0cfd65b41f682420044551e5d13a8b1a35798f71a3c41455528852065ae06f882ee2b5024c759475ad31943e96902bc5374d16c0434e32b4a574b38ab0af9cf694065f5fb3508f127006c9edb84ab63e42a8884a0a5c21f853cd07d9fd32b0d1d9ab4851a00f2bde3a60bc2217c0cecde13c4a53c5cb5145b4a5cebd68a7dcd66e8c174dcfb35f60538b11adec6e3c81202480c211239ecbd1083a1c7e514a6efab10d3421afcf0aeb6650be907ef6a2486fc483d491e3e8f40dd07341b0f1e7f1724f1a366b22f4d9592ad45d7fab18376caa45d8b86278da15e1b2adc1a1fa64f6b9b3dcb3c3df966c7effb211f89a648c49874576e284795de1d98c93f5ee8d2510b5ee8f4a66b1cdf4d2dfd8d17b97c0525bc242fd2b004628ba2da234a74ee8712ce5be4aaf23e88b2bc502cdbee38e4212ac67e35ce930deb89f982b695f8fb7cbcef0a757a2cdfeef87cb41a1100d5872e28ae08626825ad8f642b8a6277f13db1551f3595e732f86fa23784a49de2be1eed30d995855b9a864f53cbf5c0753b81a1b09f0d2202a5d0212d1b83028dd42c91dc9c62232ee732ca674406087250d3015d97e96a9d1698b168cdb20a7189f55d0233dc7156baabca96917ec63a85bf42f7fe6fb3bd640e2944e9dfa04a99c66e24e0420ac680af9cf53576cf54346d75bbe8878bff972be5798486c9e4a5f7833b8e39eab41d690f33f8feb78bd632ee3332acd7e89bf0e2ebdbd06702529caff9b356623ab22671e49025e99a48836bd08d66e4399f7d87f4893ca938fe9251d547d2b3479898d19fb27bd0372dd89f0b5c028f5423af90b79cf4b62405795c3b9448b57ecacac0199b32573f90ec9ba9484c0716fa5901cbe99de1802db34971a8305ab54cd51632ef5c416c19a6616021c53692a43c9f5f6f52dfda726ab7ffdd244a8e6ded14fd786bf99b2ee06e80580afd46ae176a46dbd0428a2da46442c266a4643d24a66abb077f36432a3796df963605e05e7592ab6945b0f852f9fe19f3dad306e3a51d42f0baee11eaa33286687e069845e770c7b14fddefc9e782593eff6e6775e1c89e31df15e512fab3c741cd1c421df33f7c657d889425981bcbf98e93715c0495b6eae1f62c10374ddb1bd9467b42feb3f3bdcca2b7b42c6dbca6a7702ed7f27c237ee986f25c5e3b92dbd01f75895e6cb0facbde69f32dcb2695847c00a0115a268cb55b19b0b8d09c8f8d01e13ea3b377cdf6add5637d112e235dc84399bf84ba514b9fde676398eaf471b8c7dd4ea421104daf1024fce76b8ad3f243a6de82d3deef58c7be466d3a15f7a54c83ad257ae9ed2c7a6b5935170a278b5115da6d27dca7fb3e2811a5c908607784f2ac8c8b0180a4096b89d1bf497b02f60d60fe06271135e9418612b848891cb8753e31152993f816e83f8134573bc0cc2bbcea35643ca6b9bc4e05f4c433062c47043317401d8e1bcaf90d7919e60c4059dc5ec45f876e734ebf6ce7f6a4e28e7463952e31c44cf91d941c328dfe03cb47c2663e3c2095a5af784e66db8bb3fd1030ecffcc64613e37d8370d5266ff996d169d0b771f14a2f1fc51426be19b40baa14c7960d5af6b3a6542b5d1ff27678196f04f40cfb883621953b684b3ecce5941d25983f34c40b66723fe200186cc8cc6c2311a3bcb331947272fc477a0e52f7e0476c7081b99d17e4884149e29ab01c234c7607936e9d44d462af8d17241bf6b7c6eeed8b8f183b54273324d7bd8127df400ea5fd1cbf57f43887daa85859a43eb5517d58dc3564a5abefd2a7512ace33067d983a2ea816179320049703c02c807f265a5f26e6d93e879d992d2e82aa93990f8ffcb2fdd7501e98ccbe100b93c25ff5fd4d15d4174ec931550161c43db0e19e4b5ec3984c18a465e0078980cee75e96cb40027fa9df2f3da6f44febbc3b70f1ab889f332d0ccb37546678a3bc977abfcbc430bc1f4ab76d565feb1be416b19462e14f428bc946b514bc417fd8266cf1857654a593b3bbc9ed37f4ea8d6a430392ad63819a1b903d8f9fe75922b8d7d90bec25149f9907283a11db2b9b46523820ed28584308f3758f823f6c1b1b11e74d90463e176d762d4747bf2ea68291e1f5a59c389ef472277a00f29402bb30f1aa6450a4c12d09401e112bddcb715081c6fc1cd105ce61f40cda0359a1fec7ce3518d5a575dae0888e9433bb0c1380f6a6fffc47d8f268e899bf12847bc803c19e754173476048dc2c146ef9c6dd3f7b4346f118dcc42400183c55df5fdd0bd2d80a3bdd3334e0bdcec952080ce6bb6ba89332bf25f0bba7d97b23916e76b1b4fffa03f97f2540e8aeb7946345bb21469988257d196ca1b67cbdb5a90d235e83ed32407975be891ea3b464205d63d9f6d60828ae85a4add0619fdac9b56f54b2116027b4b44324094cd35cf9b12b4aa22bccae6e27c0b83aad84354644062df69b36125330fbe32585f9020d2ca6ad3ab05c7a9742b3428d86dd9db671e0ae87110585601736363d4294c94420535795be1b2c9aa8c2d4aa24ba6761fe8da75a4ec56a17c70da70d5898a318edad957cb4e1351de914220d627af329db40afd430a403cf68892de9835729930fff6cb7ca32f20f38948d9fa61f8546d175baca56b8286cf89f260d58d26a20dac04b786782d98df4bfa7034a6556979fed283062b23d5f25acd46aff33e9ef03f5c5a1febbad08b1ceffb70048605bd578bfb2b78ca47d7505035198550013fba3df5fe725447f18e598565ea76dcbbca802b9825c484edbd0f31ad23337c2aa62cb852e082d337f959e172ee6ea58d96d34bb49631b815af38de81910815861a0dbc9a184945290c7565863698b8a5d9bd5a0b3c85cacdcc20c22f287ea9aef6834679e548d7cf33a244b22fce7a8cd24b98149325fa9cc935f196984b098f0e9a9f891d404bf90bb8d5adaeb7e75cfa706d33a19b08ed93f7c48722919eff3329aadc9c05d08703f366b241d84545fdd631f3bdf74a70429944829bda222a9b5b621ae80ab650fdc3c6b6b386fed6d99cbe5d341fc910ab35d075b93b5fd17d0572ac454e9424a36ca9f6cfc6232ae9b7948cade26081212435139bec4b9fe918a4a2b7bd918d15865ff7c5db8e8a57c9b509533cc6f9dbc369c33594205964ef047ed3032480f57a51daf0f50898532cce9f632b026f03ada662cbf2cb674d429a4b01057da37ac63ad80cca8656d30c98b0b30862ce6fd5e6d9b6c8ea013a1d3a002e2361bf80f831f9afe2d19416130425c4653c9c0bd15601955d98e9a683e396f35861d06943780cf4acf14d0f60bfd74d7b38e6a2f9910d264ab490e397c4225a7778ecc558adbf1643892aeed80ad5ff58321ea645da1b7983cea071faa7aa0229c9301c97806cdb3a0f1816914797e2a40a0cdd60cf39724154e8d7b019be4f8ed3aabfb2cf2e57bb45fd91ba504954643d926b6cb445e72f6fca556ebbf829fdb261276ca380ba918784a3ecb57a538f6221992f8dbf1deb492d38d684394e1967e88c9edddf75fa0260c05ec90fb85d18edf66124efa0c7b9be74316ee259c59c29e4504ce9210493254f3bdf19935239e383373e78daba2ebae1b34be290ab10cad114f2d5feb5cc91a21a5554483a399f0a7e3c65aba272033735920c0505ac2368071a91ad0362813f1f9d85acb68e322525f9e2e283d6306caf04331f4a8c4cc269f48176cf84f20b009f1f04b44ea3ef16ff9bc5f6bb32c718c465f152d09058d23faee9623309fd4780ed1085b307933f544ed547c536cd75fb0be641817479708c43a2eb89762eb1b9badb2b1c1951f9d04238908f342fae6a64b15097d621b7676d41c390b8d7095bd9bed61ece6c61a94f1c8d397be96f1e4b3cb45066ba476aeb49c242b5acad77635f29b624107725a87f625a56c2b82b885591e4317f2e5c6609eda1dc12c53aba8484a20ff98e164aad449bf3c2c120a184a4da60a1ce36a33e42a1e205b3e9717a014982065ef7651f87e40a41006103cee135290fe95affe5ab92143362180a32895485cbea8fe01de534516c6f0089418d52522c8702ca9cccb131c652db6139159169eb4c648d2809e2238822de9bc7b24dca62515c14a48659ebc5391ac13e56b93452f6118be74710847bd141f37e015c17d2c02cc0251cc3ac37175939add8107d648d107fc5144455026b6b7029187841ae63613486b7d9ae5469d1496e6a8684b3adcd404c094ab9e4010124d3dca930c8d05d12daf9b4ef219500a66a48512aae667e27ac4845bfff9a67b22cea4b13d9a412139221c42d82e4c60a8b211a2b847f7bc436b544ad30a6af65ae6b90c9545d59911c0ef867021934f5da7443a33c33b0156eb1941640a1b378d37869c616426570922458a90e610e468fd35d66c067fcde095cdc243e26b417fb15c6ca3260b08c184efd1c3c8cc9653712ed35eef44d34179e892eab5b45a8d3bc53855ed42e4bffef31caa3f3a8d17c70df9cd20292712aaa4e73921954a36cfb468fce2c9c4e8beda9042bca173fce9be524f9f2fc094bad23d3de37ed096776d9138a23a20b6c0789916d970f42ec862512a959dac4690e93cefef7b952f0fe2058c44f150c570d28f8a5d0c07bb7981ca264a988a2cccca98c449ce7aa59d3e5b28f337f46502d7d78fea8102b68065f16871130e8731f171090cb40f2eec39408a89c75418543752e87bd5ab0a2081e803a0ca141b47064c620c15654c7d4be212fb582d1563a995c587ed54f3a5ef12922c65df2b853859451c9381220626a7e912a18549a4242ee95a26915ed88253e5bc1af85e617e15aa965de3287c9a968d833ce6502129b7224a643d9bb1a01794f86a5b802b5cdea433c8fbc68f600b613df2bd41f4615907e308871254b23ef85e4ef9abc4d9dfe99798dc0aeb97d107fd542123a11b9aa592c84e2037e5ec29186971a8c6dce4e78d95922da1431f7c0ff28683fb5442bc64c6a267c07b216647693c3cbcba1279fad01fdf32d695534e49eaa4e86cbb52aa993854fa6e44ca54d3ed305e2a084b0718f6a0178d00e6ad8ea17050af510daca3afd2d714b6d2c553962d01d2ce5f168697dae36f44b47ca46f4c64de0fa6b8dfae0b2091b979aef9b116bb0cfaadaca5773fca38e8dc290a82de2002fa7cc6c02b23181c58431a6a99dc541727ee077926de7707fdf1e0b62a2978b3cdc463aac40d311f840e42aeaac2cb040171c24bd2a30f886792cde82c55240a2d0b5155ea02f92a47b324920f7b472f62cfb46f332c12c7c2e672f4a41511d9d11eeb020d95bdd494eb1b10afb4e65cb8cd3a18b1ab40066c03321172c42f4140353866a788852caf0006c957af148522e768279d766bb4a119e0e07134b3b17b04483f7feb45239c19982e0573d9ccd422cb00f52f20ca38ce3f7b41448c6ba2ce114626f31588f9b65124cb1b5800a0af29b8cf335d091059ead14ac5838ca1af54a79fc66c069e4d580d02d09443f4271f097c2aa332716f93ec3f53445ece18c16dfd03292c5575fd19f010ef398d8883b0c48809e9710a2b381f11c05cce06463a866460624a0e2d1709197620df4f6c2f602f643f023ed728df5b3510ca1e5d8edf365572cad7d2027a699fe8fc2fe74e008339a39af39087303c463846f981121ad7f05b26d9d90f06213a43eec74d473fc05f1aff4e217626ac68c8164a426934e04af43332f456ae0dc4a68d91d46fe5d8566c6fef467bf6608ee472808f070960e69fde29c409f142f233eb405258db8a0ab222b3e4cf6972645f59fe54c38f58c3b623eae44382978d1d5f1fafb6713f1e2eb520642022215ac9db28113a070c6bab209826f78da6c7bc806a4584507aacb8c7810afbb6729d80c161b370f0d89db7d04393cafbbbd8bfeec07bf8ed649cca3798a6d3ff53c2c169965b237629b2c5f15250fa6b0b34dba153cf8d2092201ca65fc21c4d10cdaaa46fc260f229b803dcb84db241403e1246ae5c8683f2df23158841e0864c574066cdde8e87aa4faf1906c522be92aaab1d44ac78f339a4c70056c8b6b622d22c615661d118a4d8507e90cfc5fd9fba9db592fdb7f28cc277d46cde02ca40a26be1b4938d77769256d9354fdfb6792397ec2a8c5b1818e5576ee83cabf445ad4148590890a3f1e5c43a7e51abbca0fca7a35239a67e7fb0f5190848ca9f610251d238027aa9168bbb0c88592bd10d99c1cafcc5d1ac3c2c9761bca28624fe63d17e2df1618a4d70bf73828b09ffedd886ec562e8861333f5ed01564996705df23f8f1adbd53a50d6f2cc6b5b6cf4af521395c060a3988e9ff9c313b027656dd6204e8e13c21a4df0ada3a9510477637095104a833c351f2b5f557c0c5905030dd6628222ab1d365900375ede0f8a72dc4191a7abb3c391aa951cd7ba34b40c90ae5d172ba63de98dd269bd9eb469564e4bb3632aa460893c553e7ae0acda13e96958f617aeb8e12318c8ae6b23501a4490ec5f68de024113bb729b9ba0a2352036f22ed279c4d88c0e9b04b3484c5583943977d161f6171c5e13d7f849a9d534d72d013a42d5818d6abceac463d06691cdf8da77a09232b2c56657d985cc49981b173301f3ba4c077c486a2ac5c3c9f1f3b1c7855b5c5ec2d9e5764a374b3c37ce0ef5d8108f08ce4cd9683037fcee0cefabba7f240021c2567013ce973851b294d156555dadef26711f46ac1e128a5299786ad49fe6a2288fa6985423ec1914b1a918beefacac8eb0d0531e753856d51b20899d1508753670a2335b8698498666bd07aa27149af5eb610779286d62ee85d207fa239dcd1669e61833a443333b28a0d6db6f1df8071895b16852022dd4e3d78a33137749f6cc0436bf71e2d806488e206faf1a8af7315222aa40f6113895ca4511361f1d1f91b8336b2a1ab9a1adc40dae0889b154a6653ec221020cceca2cbdb752fe8f194e78e668aca00bf0ea3409045bc51ec442f8dab980eb7191b0169e492e2c56e65c929a7ccaa5ea94f7739c66c1c0fe4550a3f0746a3987b43461741ff7b42e484f40fe7e6b424c413ba644a715259262f1b71fa80277129bb760a62c5f98991b2c4d6cec3ace91d5be7831b1b47c22ac29d21600485be28d4327cbfae15dd433b2de582717fbd1b0f0538ee668f88080704ba635b3b8f76639e9981a72fe70f8d2fdb59201f9cd69a36d18b8c7a29cd1810470fd9ab19f74621101a0ad822c69c5cf79324b80eb65f120c1133cb23511c7dda91259881e6cc56bee5005d4670e60c11d8470006d2d1f530fb2d7dde4cdbb910414cbe9c842c0e7ca4deb0c78864daf616c4500583932643d85644d76d4adc7870d0db3a51f31e127a23feb3ba42a6c2e9269f5cc219fbd3b3bf63cd9f192a783259a4cad6a91929e925e04946f6185ba87b1a3282158529f7c8bc113a219d1b5c963c2e0a2171aec49f9fe5294e56e00b77844a3f64c22ad1267e90761a89e11b9358e6c8cc286337e4c962df46bb5b5a39996c9347460e82569ad42ac872e3aa620f4900b1faf4dcf256c1f8d05b81aa68a728370a02c365a705604661bcee782f75100625dc6c711c1fc99b3a567299d9c19a5d346ef637284b07ddbab10da19456b84c44c60118937078c0f484aa4d348e971a90652d5a69b576f7a55818899ca1112c1dc30ec3d84e2a537445be77781479f85d43b58a87f464bc761a2fe0a575001cc13324125eafa786e14c1257a45da4c11fcc1e449f9fd8ecd1297d7d7254135e174e06f2c371f6bb5ee1118180d9a5b8b0f74517d10e1c073ec66aaa143091f196be9b0af03f115d2b812f508765fdb62c4b6b5adc71d30ba4bbcced166790505e502559145ed681806d3586084d94fb2351b28f11b078f45279afb3c53d8e292733e283e23124549ae84ffe85cba7703b15d161585bda2affb634c699c1935ebfaf2f5244e008fd03e0c637545890440708d0a8143f46a707a0271236fda05562e6143e26524d251c45b44bdb907d001e4b3e652dd0eadab133e5a3b3efa6fc45a4d09a9a08f81092e0739ac48b4c1e5b7c2dbe9ed7493e845a176ebf5a216f051e61e9357ecdaf4351cca4d1ca773b1ee3d3e5dc49a35ff7584f2c6422bb6305088ff8c02ea4c57803429c00de49dbec6b333789149eef1fee484a34528e1181ecaec326311f070b93171fd7dd1b9cb9e56ced4024c7973818358ba22d3a26f71036784a12cb6e313d10736a932f386420e1a0af80f34ad8827083805acb6108c6f3b95f631edacb1e65680e3e7cf84a8083440a04824a85b82870a2791ce3aca38911100a769449c77c9ede91172c403cb4bdf6420bc199833b2378cdca0ff0341e8e868448e1b2a033732b1da6ca60d9c7e64dfa541e40c239d9444684e52e81426534efa9ed624d929e8b842904b37f399c1cb8a4a0d9ce766221637d4db48105855d15b20644012d32c85cd4be2a1ae14f41c4d14d6796b27c6e8c1c0ee72e0797cb6f76625fb5d1ee03a29c4805d981a7d13c226774541562d8ffe522734eeed1763abd24099fc532fe0a507fa98322a56f39a87aa6dc7ce9d7c01e005b1cfd27956a41e7aec392ed358e3b1c2bdfe98e6a990434bd17f8f12590f08cde1ed16d75ef3f8a16fdf230d1424b36c67e4c6b5aed49ce7f959be4b4ecbaf4a1a0492a7e343e4f1d988ec0b5f987ddf18a0bd491b6b4a53c02d83bcb43b4e2c29b0d2f11bc72c7619d7493206934f3367f6f3aceb3f93a9deaa8181adf7eb98983120563dfb8e190fabfb13e0600ebacc9df51b4c7db53124774de234276988bc3a7aecf2ee022d40c8ecac6994ff325176c58a169b44daf1bf1c817de23fade073608f2020ad500c48fb575f9bcfdf48e9cd2d349d3e0dceb0245b114934b2d8c04de5661d27769ca3f2ed1a118750108cff90079bb90976fab8cbe58e0f9f00fc234fd22844a42e215db8648dd55da30f4bb34a59413c384d7a8f6f31499d72a3acd7f1fbd99dcf44986e547c9b2022204c3a51ecf4fd937e4884b42dc3b83fb62f12e19f4be4319c881c150427b7e29beb552f48af83059262856623a714de22bb4ddf94bfdcb08fbd647bce37e7404cd00f92613ac329549bd15b6d66edde3d315aa933a61ab9614a7d8e450887dfc985217dc4940397f33cf0e018237807496bb64ad5018a42ea0055da5049f730175bd1b941c0a5bfc8fdb12d6ec6bcdfab46f50581e5c5d1d18abb400d2ecab95a0a3a8bd32a88cb496fe383a5042b47a68334930639b6e37a39cc2426172750828f3f742e0f16483a70772e987099877d10572fd344703a4cc9254b9b3d3c9a6748e388417333638ed93f5600044354d4ca964891bb6b0f49dde6a865745db0eb1f347bbefe6e4197d63b15794cb1f2f472714382f9d87180ec0876127fff3b637481c00120f1007998a3f6958eea2bf69ff41550a10c40ce1761c3a2f27f4862f1758280e6e10b1034b1d1db90ccc90ea558e00229282d3e4e9d90415e8198d8534fc62fd5b737a8e0d19d890640c7bea423924f06695cbf2f788e5dbe55e8eb53420107dc34fb7cd8dde4f3bfec3fd2e87614ebaddb1d41646369138acdd9685d64be14b829daa360533ee65157113d048a642cb4a9a03c4e6f18f08f17e1ff52233fcc965205362a8412f609d685120f3144d6ff88d4b72187837cc18038c55214e258099fb80a0b160c4ed2abcf9b6eea524df3f79e8fa4391f7440f3712834002d5e13918f900bfc39c16f97a6d559461e430f7cf92cbaeee7111cac18029f7799ceacd579b0d42a7c6635aaca1306713921c6916474c4f524bbbbfb686957646ec918a06e3ec08591b01c17f1d8e625c36fb929924ec4a88f95d2fea6960da11b844bbaad7c03ae5860791dee533c245d503a46b16866dc31b825b893748e14b4720224a60ff4c788a6a087e6c6cce317af9cdd1a9ce6fcd4021acc60b20c5f464573d17071ce9ad3ea542bc9534a58ebd3ea68d3dde93942adff0c97e9fb6ce7702ef360586b33d2649bf0c4ed774af614dbcd3aa09b3993ef52d81d4ccab155416159ca6110280c97eda38f7ebe57fc70b81990f4091673b70c6e22befc5da54da0f7e2225a0fba18ec7a9827d594c641018cb12342f5bea5cdd2bb2d95418f974203dccc9e8af94a7c447af6d57b935dbaa976ddfab64c962b526cc7a59f06cf84a90159ef6ce0eb3b92833d2c9491187ce4530ec99cfb4cd038ccbe0c1803b5c06c251e8a7a8656a91dded5828ec55d802a0033cfb9747edae1f3a0d72608b85921e40d9de34f7fc1907e2c1610104a7840ecdf2eab0ddc3dd64f5dc6e17eade384721e0a62ca5eb0003d54b018eeba49e23479cd76af894e3d29d6f00e3367676f0505a616a75d05f03f1a1322addbeec039dff1c1502dc133ce545177e018670890a35d64e4bcf103dca5a596397cc792f578c66cdf06062c32085ac3738b6bc9d6691dbee16eb164a85b8e7f2962aecbb9ae17a820626d07ed2cc93ce4dad485d31655a23de02740627725e3a2d5041ed484ad10cadd37a25c36d86db58cebb8a5d571651d17ec314d02a9230933531ab94289a6fbf2822c4cf062f624fbb6f1ff8a23e831e430a4b78f44a0a19c4dcfed2eddaf08fe8e00f24f4082d883a46b4a81da8934fcfa9b3d5551c75fafdcf68b5905350d033e55906591a09917c1396219bc4413e30675ec4fa6a49f8189572d40381f1d53fb9c443328687e403ba931bb9b7dc72ac89802383a68eaba70290ff2c1be6fc88ab32d62d8232db3d85290d4949699b3778ea04c41134403ba5ad8936f33413826a9b38954cf8af73305635e1f7ffcf282d2371c2117b0be0a8ce4b1facaf8d76a8a967be7770c3de309c111a233af287dd8155f10985c033c703e41c04700f07deecd3f5ba6f5af2e20624ff2c73b04da8caabcddb10022c8842c21e9d06065cb20e7f3df9c0a7e78be50ed35b56afb2c582837538970467853189e05377f77cd139b0819b8605d93bb5689f510b42e25e36b17a0721bdd13d15b129da3b82e03c9ec9935d625c2e36e1095872f129b25b2f2097d9bef24c5b2b7b26808f8315e82d0157a9c951b7542addaddfee165adf59a87cf66d84c962a5302d8aa3e0c4b79b8eaf8fe2af92ef9bac3c7c89825b6d416895e2203f88a9e025c906eaa9e33f9874c11d0c0ed3f908bb3a4af9221c87709c3318a9d250e4210bc7b01d4c2e61de4b7ddd302d95794442345b7b01ec1d6e5a6a71422d30707948273b9787d2bc16c7cf32ad2494bfc2b1ced293157830ba77c965f5f54141171553e5fa0fa10482766a523d061e1c5f46e1d20d70428461b7b3e5463c5d0f6d46d64f7ceb88186580c6c56884b05b5f0744bb2165f6ac97b93ca650ebe70a341e6fd07032f1aea03581b25b2ab3c0a02f91edfe1e7d2f82087f079393a3efa0010709934df362eaa15e20be81354d776134da862f7d3a607dc93c2bc0a1a88d45737523b8d99c4b24ed27a8e0756cdfced42d24d11d9e50f153c9914e7376d61116630add58d10787dab5295170dd1781154f80580b2296c7016f0dff2fa0a7dbba351dc2b848ade144cce2504be810d4540b80380281c60dffd1cca6f886a47a9f1ef3801e90d151d1154180892d25a063d2a56b96ac783c12dfc62413179d9fdf33d1818124e927c4ae1d4f9bf8f25aa276a462671ef4a11cf0b4222e1721ec5f64d17d41de39fd885a68939453b159509d8cc334d8ab00ab360048824d28b79da9dba7da8559b3eb2cd090ab32f9689a2e0895909b40ea807f3d59bd98169e723ffbb6c63a3c93722138e23ae30f86900d918ac5179a7db8504920002a4605612fd8585700d4d62c836eee76831f0b888db59c58ffb77a1180fc505388b343ab353926dec99f7fea957b04a1c32f10c2674ef0d206440ee1e6fc6d2dcbf7a112043b43414d267c6631037324cc16e465b520dc24379f3aa35506ae67c8a92674f8aadc5f132b52c8b358b2aa2a6c4ca2ed6fbed9df484d339a067f278c972ddceb603affdebadcbe7ebc0f719a46e4dba8ef5f5495c89a8aecf94cbc7317f71f0742d3cf276089c11b8304c8c70981b13f11108f5c60046a268072edfb7d1ea94216570d854f5484961ddb377bc17f8ae0059f6173421ddfa9c5431869b0d1cb63ec5576db45c986d59cadd423a211b924568b97f99ed748bd4009f758ce23599bc7a81fa6694ec28941aa647675d28fdec6a33daa33fe158d6e1b7d5e1330d2df796470e1478b15be05eded45b03dce11d681e5d2865f3d3d070c9d711a22bbf702eca0359f6c399e36503a45437e2b3b2aba8b0145145e0a04587b01b88a742e7d64433054679bdaa28b1d31a65557b09052df66c4165cd424f03fbc9f89ef59bb010b97d974c2416367ca3cee55266ba0e9b11dd4c8ff7b4c338b8f6439bd99862a07dee9fa1319421247838cb70cb2f97474031e082aff9be88831fce1a8656fdfbe944b4935dcec3eed5ed0309769f67aacb29c7adfda679040d8589c4ebe56cb33ef7141e7db7ff3d4bb6941415662cd6616a7e7d0b9f066475a764e58a17f1310bb9dd653336cfc9d2f9cbf2b6aa03485516574c1e988d8b509695278aac86e37061b132b546b9826839b33305ccd196cc9a7a4e55a3e4dde815a3037217750eab75d69963b5a85f06f29e2e3aa135494137e47474d32a0174bcb8b909775e84490dced7b60b44f11c5dd2c8f2e9fddc03674782744a128584e9f921f0a5d35209f164bee8cbe4a1ea9934116d605e53d536aa730a96911a6ded5b9931c5d9446c0e17ab04ef6814a6f2781f159e5b24535993060b06964f19e4d89efdcd7f0dd3177cf0525dffc45435417a00edf01ac8051d211258e28f61a76c82e3eb751d524ae292be8746ddbaed3a0f62c2c2760ae4e2bc8ef97d6c089ac09709010c72cecf29a720bf718bf59fe2709729165115c0f9fa1d4daebaf439326d1e1e142e88088f48146e8d707c8ee4aae1da9cd0e47229feff049d0601861bac4b6ea3ce095a9ce823e7ee05da1b5c9d6f7005bb4b305dbcd10014f241f3e4e5cce03946f5ee11482844f992820fa64344464c8d20148fc7dcc0a4c75fc465e910d96d8a822440840e1a8365ec8e63319c534236eafd7387fa04edce1d053d51893b9eb95ba4a40454b4ae7f468bb16a2cde7df4ce72f1721ed57859770afef63f504784e95f7da17b67bed0752750f50a7641032d40badca62f008278ebd4ee2e7b86944fd62385a1c77f6617fca5014fcc95155a6b284da8f640a3c278bf7444f5430b8731fe42f628193cbf543c32fedaea7d6bffed24e65e947f67bb08ffe586fc25d78b45236bf5be19953b926764922f2113df087d642fd3e201d78ab3abb6e922a83794dd0a939744d00b359bc2d5515a80013c00f1d36bfc00c5e2e13f78ab060b6edfc8150b253bb18e8fdeeb33aaf06898d29ef6a7ae37ededc8e31b613752dcc8a2f9fb9a4227786e2aa631714610514e142f50c6ca161c42d201f005426f9fa7e83e7d8854943e337bd156aad34db8a6b6d2062580b14238e7a029c01b31aea228a0ed832cf65aa0ab9e8abfa81a3627a0abc5d9850c73b5c0ea8602b307a20aec08467b0f8207cf2ea52f96ae1b179f4d0ab387f57405ddad42b84307a1b47cc801adf4b9918300c3bf6c1584001503a624aeb07942f1fc42e47d5e003d1c1b0f658da4f501e9d9919c7d6941fc903beb062c18c7ba0899b8316e10907ca69936288fda1aebf399edc3ee9e315a75297b5c4e10cba3c2e468a60c079f66928d038b300612ea624818da5f301eefd79369c0c4373f196428ee8205d1a5766b7c84f418464c52157a62453c40f2403e2cc290da24effd180f31f8f571a63a0c5e70c3b9c5bc0a04f1cd67d1e5c620dd82b8bfc15e8b90fb0e4585457ebfaff61762092170a1cdc6462b3aade4de42744f99f8201b5f3d5b3ab571f55db7a3ff392c84793afc6a388406ba84aebce604a75972d2613bdffd77edb7ae89fbc717cdc7c4c70c84d4347f472fb34c96abe2bc9d0601270592d8377408d2c76d3440c04e1f5f416131538f896369c3a80dac3afaf127f6fff543ef27e81cd075554adf27a67d7ccea699a6d4cc627c1c7a101d08c455367d9eb8f6294bbd579b253834211dc040cc8e34ac72b7b2ff45516948962c93d74f89ca24e37f0c548fa8911aaddc2c40991129d7bef38a065d814b014cbbf9f41e232085355241faefd65e140fda53cd3befe86058760ec2b8dbee9729620e333239228965c7e2994a5cda298824119fff613a8757714c5347dc3d29f76e4558ee505ef4bc034c298f44ff8453398c0b29a797c3b1d8bfb34b8dcfb0a3af8041f77f35f0eb709f0d850aa9fa53cd16d9bbe45dd75ed7572bd505f36d62deef78171c2690a5a6aa2d882e86ff5135b9424bba1f511f73d931d9e8d224943d2e4f9c7dfc78ba1939dd522601bd80f254595d0a8ad27b6735cd8537ed3246abdce5ea074ff196e66058b2a1eb2b14ed06d75f3474df8ac7f4fa8786ed7dfb75e3c08854cb73f7933e30852db0a2060628579664687323bec6a4d795a6dd072dd7a281aceaaeef6b996e25563287e016e21f2eac476a0e89a37da5b24b1758619ca9d4fd9654a215cc8efd61dc49314650118028c90468dcb7dfc70f609bd6cb17e80ef929c8931ed5bae485d3f250b4c8043c8118a50a7366d0c150cf35b10f3206217cb9397f80406ae932e79031221436ba58290b89de280f7c0aa141da824bd7e1330bf7d5198575d0dd9857c09432ad97dc30db5ba7d8f719c8ba39c00614f10e9e445161f849748a51e908bdbba90a81f0434b2bdb8a20f5f8a22262ce20e4e888fabb229cd62b9770930ee4aa44dc07f650da0a538a85134be03ff4f9c5723a31c2984f53d6a2555f2b9880bc28a5707ac2a88022613fc1e454cc60c08726036dcd16a694e806ad4e7773bb0538fd816a997ad295dd4be28dc39d07768e1ebdc827844c51634af9b7fc8f05b82026849b2267b604cda4bfa840a843f66769aecf08f482951f3a21b882d4db971aaa5036eb5de65ca0e4ed3620b9cb0c6914913e2ae6cb59628106e9a4dbd8de4bccedbb12d9f540123a4bf9c349450e0e10272f2381f262819d12c406da1563c0db61448db497552f74d61c18461761a0976d6f7ab6fe20753dfb5c47cef784e0ad5bfa62020cfefdc6468458560e114484ef22a6464075ca0b2591545292ed3e30238543f3b08e30a848082cd4100858898b14dc246d236d97d1050c520fa7627c768d8d682fdc63db9ef4608b2b0d1008074a800298b5318724820c84d9c1e90eb9f0f8bc574e1691f0ac33457bbc9bcb7149195fb3dce492dfd6a61a31ce3e3a069d77d818a45312c94306b23dae1acfcee74a258b701d2476d09c564390c8b926c46d99999c0423fe0f669154e1a139756be4bc353e73b0d0a7703a47035c4a80f8bd8839739e22e2b830c7c1cf9cfc7442fc01383f479e20f4e8b4809f3f11fc7aa3932436ae92b004676c74a8a03fe395de86bc2a30179db5bb4c2c8e526edbfa83a7e63e428768dc2a1195380a8ea4b90e3e77576930fa008433da3b1d1d044fa7babdfd3020c2773bd2d93619a7b58878a5c1d8d54e9081a654c0e920bcce90a233d474121d3b5ed22901d5834128a392e3e30918cfbd8f756a223f5303f61685a0fe058cdd27c4efde1dd77f85ba1074c796df37b3ab3462c24c9012e08fd3ea1b37ec981426c87f965f11879f0cd9076f2c26afbad9b0bc96d8badf2561ba6b014a632453388ff3148d3170b60e0a7f620e030fce2df18598981510c4b05a122dfd01cccfa0d1eb3132a7ce5812869e7c1cd613fcb78d779c551cbecb4288ce059d4e4e8720da1da87c69d366b373014e05b69a028b249a183fdac9dc71afa336fc1aa0d855174e09b06318edf085ea832e9ba52380aef1b5021c2bf910fdbc7653401b45b1b1ffd256ba8522f2a80711dde4daa7c66963926865fa6f699026e83c37d55c17d734b3f06dbd3e53ddc4013d318bc76dc19b4077b10f1370a548bc3e3d6be3b458b9ce39a1277ce8c825c64b1d8797c117c2b777e9dd1a3e1652606bbb9f07dbbfdc0645d6dba36b204a40938e7a83e33ea3b5f98304796c29ecf0ac273d8a3fc61894ea38ea21da248d4172ea10ada39d9bb751fe1cee289b0804a5cb8506888b3f92202faf492b17697cf299528bc72e2a08088df1b3a800f83e5684ef2a3d90851a935a2c9eb0f0c970ad6f085db0485e6129396624d89f63704e17f56529219366ce7ffda18c4f8d17b14bfee5707911a72b307204fd9763c022f6898669d6b14a98e12f08c7ba28c3bc18c5a1d53d14904679f1e46d0ed2d8db733cad26d1d351e5244efa7068c687143ebe9561b8fbb12b7aa68af39d124b537b677ea76bc60d3cfb474811b453501ba26a9f4be5935cd9b3795f5fbf7575caad2feaec345a7d0e1831ef7207e93714aedcf17d125dd3f695376a9a7301e08984ed2ee38e76c2802b90381ac03591fc175d4f8572353bb60f217787c7dccc5576efffcf5f63248596ad3ee818d6faa7e53af7bce91f31d630f9217cd46a9597d4ea87a5ddabf17a0114d6cfde0df2b059987d36efaa4c5db8db0fdb8a3bdf6bb7ff40ba688e4dc08d7c45892a523281829ee882130ea5d4e39e602926fe8742beeaed2a0a1d2c90c5ed0f5716d526943e0e1d98ddc1b8f5b60b3d9a0c5542025646a6d123dc19f3440741dd292a092b6646469477ba2826307c0e01f2aefa2220c6eabb7452f65377401a89a1d7f4a55cb3ff7e20eb92d790250e5f468ad37ae0c6a33b79942455f7a39a8ce01b22cd31b0a15c4e49c4d70f78af92b3adbce15b7122c36fc50054c9121cb95fb703588da26a4a1621e1d5643f7d90f63b1266725f29e92452f7d627e02b6a9788265e83c2a9faa3e6bdbe69c38716c07ede97ccc2417682403373228af5f9723d5e0f6f45e8d2acbbb178a07927d0ab90f5ac5df6b5a67b410822e74256f8a44c27452a327d86cc86ac928ff8e44854892c35d515222787bbd2fa87c56738ea7f5f8ec95f63c109709fe309e9d21b344701f18825abe943b2b72a77fe2f33730baff222452109b50807822d642b8fcf64dca9df9aae7ae2914501b65fedb8ed13b9a166cd7788076d9b3229933ee7e064da6cb38e012d6908472da0611c6dacb1b734ff7feeea0cdf4d9bef31b7e4e03bc770d5eef722254a174aa5f9d53831eac5917900996e63eda06eae2a7cca6448cf7edecd0856658c608c0f3da4a1358d334d4f9d07d81266e6d0cb4865a44bfe7dc4392218ea4e07e1b62ee2de7595cba898c5de5ce1dba76c3a3f0a20db2698baf0687a08ceb3b9485dc60035c6160c2411d9ce7f0940d86c8a391b1d932546705b68bb418c30d422b8a68ffd8323a839a6683979f31d95f19929fe4a133ae262217dda6f11504c7f22011de72fafb2e5df5a0cc1785c3378c9ebcb563a61c56149607c7be1558cfdd968e1b340f6f5a3c0be4b3477ad9a09d09f025feda475dde6d22f382acb2afabd97b097bb860a46041bb892bbc3e1add8d662b011824f366dd5148f42e447bfa213478c41dd6f2b0d247fc104f4adaecab44ddc0ec035145d9ee3dc4404d75063923cc746a8d04c9f1a48d989df3e3c475e4ee2e68b72c22f0828e56c99c14f53939b96f178b17f8866f619c4d9053ddc10c6833ff0e6fcd1495c31bdedb438b1e81222e88481799d41b5c9b0eff3824a7502a94e692d9ee6139482aa7fa508e48e6aa20eed4c952fc0ece58efe032c12921bf59d1f6cf40d84d8c5ddcf52ec8644217df88431b212b015c450ee622c64cdac37c9ab1b548087848754cbb7c936796571307c48b1535e89fd2e58f8249f93c1c6d931e1b0e8c2e822498a49e0adc3722fa23a249fc0933fbbb881609390deacfec077753c8fab01e021763d945c6f62730dc043a58ea85ab8608bf9652096ca7d3a5c7d00421f0e71a61d61241c2657451436193d273797667cf17dcf255733f88d2e27a6ec8dca5c88a860a5eaf6d5801ecaaf9933a559459fbd8fe685e3b356b4b7599909d13ffcc442410cc4381610cd49150b02346d79b97d0a4f34f7dccd1d377b27f7b7c64a159be28129cd5a279b9226ee4854b079188ca737ba461395f6f243aa80b992c7c56024a1a017d99cb8e8772bf3bdcd07861274b1411705bb0dbc3ab369086f11bdaa1118929603200010a5f76d2bbeccc526321777d251b9f9b7a513f2362596b93b25a4ea2723419e8256f706bc047241087f030a3cd8299a95bbfb7ec5adb4037687aca5c14e88e3a047a8ec3742b367399830bb1c8951bce310139b831d52de51bf215672f53e47cbe208243d49ac13455993b9a420872e8a0a8a1680a44f825b61f759608852b9024abd0345c9d03289ca4b9907b09573422067fc2917b4413443f40b5e90aae5488ccc0febe58836a015e59511bc0e23bb6d44375f83471ba8f146872ef3adc27096f8be34c37939099326dcca450c6eed6cef048f33e064310a946e02cc1a8685fbc9a01292bd9626c2e59846a48859e4e05683daabc65627e2e3c2bd5de471c12e04e945ce3b9e7623a49af816a8d3e622d98b91a3aff59b9a836b490d40c0f95d1a1f30cd16b0a203c9db5806b94437bbb02fc74de9828ee6aff134a6a647ffab5a965795c0846dd98325025bde53ddaf2038e65899da0439e293cfed5cd88b56e779ace5b403e85c5a07371f837606daa6adcf8e73e5698cbb694cd86a941d1a3df83aebfd0746498e7724011305deb256c081610d1ed5d071de90c1937931c673875036d4248b8aec2c1abb09b4a5a321c7256fd7d8570036debb9f1359887204abda9d853927ca300bce3262038d976a4a33f486e95bc1f82992e31fe5dd700605d626262d4ab013028b12a3954db650891184deea9eb0a28bd4f5be3ad88a0d211c37ab5d29aefca456e2758159fff9dc193d36a9fadd7519620c3bf3c7e0ab9d589daabd80741153ab415d323386a0aa4b481af08b302034731b545fef51607630367155a2e7cfb4fb7d096808c98b526c3e0cd7f27125cd38bc24ef48d0be532f4058acf2e9c2d2e4cb4439aa08300203aa2ef48b9c4ffc7e5475d03fe21a68684fdc650aa916de7d851babd9999310409e0f15cedbaec864bf8cc4f2afd25785dbf7aca062f02e8433311786d75be35346f19cb1c1c41627f6286252dd16cffcd44b3f05b0b6f7aca3fd7ef3b8371861383b7efd05821484c0cce6f4e7be6a8b497fbd65c9eb485b1a3bcad7092505292a9f9c38f4a6f0e7a88f6edfd38916c00d52a2fedaa931c01a4fd3f7f86d958cd8301c1fb093d65322778799eb28108c4903a6b4273942348630205ba474640a949749cecf3b24835c41fd0a93da229bddf7d0bab8873c35558dbe7e642743f63512489615c9f72ba825bbcb9d7ea586cdc5dbf1a6e072c90080a7a820509af1f72e0dedb949c37de849caa18238e9e2967f57d971b5fe2b4be2452c6fc89b2339fd10f6c8d21ce49002598e9cdfaabe475f9347292187c8460e41841f4cae873abfbc85c02d6b3085a34d9f7286fe7ee243773eb6cbbb6ded2d0cc84c12b473f8afdb50c2df47b53f0795ec282fbc0eb9c785a4863ed7f23cc1ae18742447cb2b71ef3cac9fff1ef6b48918fabcff4e434a6220cac3f2031846a05895a97ce3c4c63672e3f00453ad5a8b467dc8f7eff1b1757155e414b960e87979e300fe4780ab04b86ba42d07840c32e0a520409870507b4f4fa93da0f5236ee49c2952838520b6933fbe5501a59e07761d0717e6e00f0457f7bb0efac00e3c7980f61371ca5ceb0136d207ffc289bffde1994b3f673676a2a991fafffdfbbb2e864a7c4870ca70780bf7a6b3f755516aa0f0adc291f955bd38a6d88a49b6c6a42ed3dc390c31d5d9804708b02dc6e35f0c1cb57da48088dd8e91596e49344efd4969604ac59a4312245668d9ac503d4d521423b56972c710c7b88232e5525418f37c2719d943cd7bfc69cc81bf7c2dcba165dcf83388c33ef15f5d08a048780391627878c714211b71b53176404f2cff43b04ea6c3723240b5697dbfe0cc9a3fac6f67bc8dc97af0d35eefb68f6b93c0fd5e25e1843c609c1a9c7ca78b5c161ce4df6d49edba7797094b489e9bd80d37eb20a461bdc09da8594109cd5d5460aaf5bc8f61a08565f535f0bca958c002a57ded9534d71d10f2f49980fc2728a51312b0008927c581a687f6cb7d0e3cb3ccb033032ff33199d8bf0a20de616e851034ccfd51f8405bc603fb5c43bcd7d68834d3843577e55d07773d4583440b019f37cae21d2561224bd92f7a35cf481daf3527a0ce4379c45530a8208212fee940ab9c95d6032f5a8c0d2867f4320d48a2833fbbf3476730c96c989c0128100c01776d9dc7a4c47a37ab04c1c9c36042abdf9238de87089135fc7c3e609896cc036a556cad9f74454548b87461ae584772b7b57138f07a22d15c19758d9d2909dced1bd4bd58cfba01b2d7374d4c1104dca17d1ae16fa6f871d5dcfefe7707e05be8bfbb278567becf1b544f73b56c44d2f542342c408f32fd7c609455e3c998e8cef10efe13acfbab899d3b8b1b99ac88913eb50e3b60372bb26eb2c040e175abbc904971ede858f5aeba798365028ce74c3a7a22e552428055db2dbeb883ee37f4a04314c37c6d4c0741c82aa28694cc0849c0adf0eb8cdd9a322ec53a92a265f73db1dfab00f66f87bd5d82858a02e7b232ef6c33000eee45780985c958de9e4979ac253b60d7963927a8def81c92e5d47cdc459ea596c6141c821ba8a5e22996f12b3299ab799dc72083b4f78ef8e0cca79d8826c1a781028585f0b155a2a7ea8cb8329ea259c5737f7593b1fe4d9a96dfe07f59654f5d130c343ea53a38a9a9301f3cc596f853796915a0af109e4287b7080e8b37f085ac39a512d4f494507adf258bb9f1f7a3a5d4befecc96f8fdca736b0bcdfe406b4ae19c43de01c097be8f4cbfc5f04992452c06272ad1e500bc63d593ba02e32fa4cdfff4a830dd6e88fe326487c3704c51f42417961f6b411585177ae0f9e7be13c7121e3cffdb973c3f1d3c6dc559dfbe16b2dabbc9c34b5c3361d098c6daad4cd1154ef0225e2743df24f09aa8ac4f2f5a55c95955f64d40951d72163d96b200374a4c75fc8a41c4a71bc20a1751259d95313d1693858192cbc6f9c26de0e544376a45e2c64b5bdf88b16ba374b8025aff1bb2c7572ba030bc2f771ab3bfb226d8537b88365074663ffdfc5157b83cdc3f27978d02a2a16200d26e8c83151fff465845425fe8dd86b8c7773d600ef2ce50f6c75f0badb28f4912a21769b619df0d460f8bd3cb9e536a6569705d838e6da6921b0506c0aa65d5982613ccc04b104cd388d06af82932414a7081be45b070f18c2055d80ed69f5151494eb72a7e66db3f1660791bf5da4fd4938466a26d83aea3535a43d6ab30410d0cc8f0992441b10306f0bafcf4599ee9b7804bdb1e4f6ba2404810484226600775f4bc9d8370e46876ffd20078f1b242231367a04d3404d92ecdecc2eb643190237bab89fe533f293bc62c7c685f4dad40101cb784a8df534068b3da274458cb0bb1e38d678b4061e369a50d4e47629c404240064105d96882c000578d60a003c76fb2b6f63a86e9ee405650c0f4b833db812154914550015689d0988300edf7f75add8ff6b59bfcd087ff10f9a72d0046b6abcf300ed2795e7e9f727987aa65f343b9be49ff7c13d752fbacbb84a1386acd1ba032e4c2e620f6524a95235b0e84a40cf68f2e1a4acb04cb7c1124b67bcc470e344089099e377074af8bce7a6b7b3c68a5761df7c1a91003996013d1be219ae26bdcbf79836a44136e376e5313d7195f8d78f50eaec287b5e84dbc3065920d55c2387f9f7beeccef202b3eb8980c22feb61ab3ecd09360afaa1e26441f50617c5750b1226ffb140f4bd090bc1bde405d54886540993af696fe80583de1be557a64cd2838575a99a6ceeddb559bd7b932354717830c821d9484d510f3e2f9ae9ab8abda4a468c94f37072235ec12b118274928ba03358d97ca80bc238fc3530afc7e1af805362722dab86b23ed5ca022c27e44aed81d31ef1bcbb400c7a99b8300d395f442b3f77f16377e58c23a250eb22d2fe26af86f5e092857cff21438f3df3617c870e81742a20e031fb21eb438d8270b8e7852e87511d52b40a7d54c8e31ffebcff6a90ea261297f13f240e6fe885cae1ffa29810638f99b668ce9a685e13c814800dc7357e97399c546813c04cae2ffcc315e531e597013634426c7344d2dd748868d617e6153c3999408c46792e3f12781642c09c3cf2a59a0cde911fed8540c4e97d58de79c3d243925591e738ace66803e0bdb1fcaf6b00501af1449601587e37d12e5df09a33f7b8aaa79e948ef0f148bb8661fd7b1a6b5ebbe03600c5c63933ffb85fb11c11a99daff88dee9f151cbf1284c9197ef55068011c5cbfd2af3585da0410aac4f21d41ce2d0e3ffdc86f4881fee241757f067919faffa27d1c6e146a973c35368798c9fdd64ecc506cdfb96066fe80efc0307f30156845354474b89f41aaefa3e03a4631ffad5358ea5d7f1f8e52b76f4947b2f803a7742acd202ac7c140a9bda15e2a8d565cfa74cd57857ec090b9ffd415abe6943abf93e117a63f126b99bbf0b421dfde6bf316bce2beee03ff9e03d0492b19ce8de7f4f9608673eabc7e580b10de4df6c7198d4fa9d7d1730388cfc149527233347890e039d961f888959e7794eba56e106022a6f4095ff2f9cc8233cef8a51dd81637bebdc83945e70992f1ac1b2a46705f0fa22652a8e69b4a65276ec41416584602aa0ac41cc8423a5f19038ad67463c8c9bb1471aac683f062e92817b24c569b42187a8d06e34fddb527f1ca085f204ac7df615756f3af52b1019ffe330f44aaa80532198e4f91bc43bb3c3f530844ec5c42388c899c410c7e08823c74944da36320d9b56b924da3cc139c02d944e91189b7d71c0365becb9ede664cc0ce49d5062aa7576f0360d962444747317ef980db89f77b66d6742bc80083a0acc3601979d21f8b67406b099350147536908c7c797db8972a6c75f1a17399200a4a8d2d089301292f46fa983a2523b17fc71a23d90b38f9059ed90b8c1fa54a71cd59001e77ddbe27639434167b5a66166ce11308a9291583758ce1beb877a652848ba291563521ac72348ca29064644faf4212c7e6d0aa718efe2012717e62c8b54671c74dfb8add90320123bdb50901f0eaba445cc7c95df300ac6aa64cdcfb5bd21b2bd1e6dfef48161d9b039e0971aa61e2883a92e83323c18e2923a7128ffd4763caa2fc0beb1bf64272d1fef248eef8d6aca165e1150b1b3da23e4b83de2492b0141420b4a26f1cd1958dfcfc0f8d7315f569e9ee8651f6e6277592bee0adbb7cd1c3dad7f468ff745952b368e17832c22405f0b0d4473a505d0cfb62f6b5f376fe994de8d8eb64299787d0ca80be8c6ddded1f0d76df598065e1b8cbe371928ca3e551a60d5478d5caece54f834440e11b121ae07f6106bc32bf948bf1a2516b0ccb837b7f6405eb8e597ee31a0248b9230da68b6a57f92b303eb857ae41f6c06d6c176d931630b305f068b975017647ba6306706d6c1f4aa98d32679f09a661acdc1ec60609c8caadeeab00dd8c9197ce879e48ba6c05ca3e9c7c8717ce99af107312316e8e8fea6ce61e6c10e0de0dd38e5dabfdf10ffde7a9494dd03effac68d9e40fa9eeb0cd410c3186d126fcda4f77c95a26c55182a365494051a0c7a46461c93331905cf1c851c51ac541997092554fd93e4883ca29111c775a63323293488e1703b15e3a7721b758555769b108ee204f64a94d95672c930b4f22a8299113cb23501b8a27b72c0f9ef2053d88c3c6e418c1eb4378f200efae682dba25827eb2521a01e9b412de35524a1fb1b5393421a19cc26b72515521d2e7ac72e9a42fa477163385703a64a380284a5eae7ac0333c978df5cbf23c07b0aa21ac035db763224a5d5a1b6d8f392287f82b71667f49b6b948b4d80139088f1b96a8595f1dda5baf13612a7f4f58fdd31506781468e9a7256491d08e08f67e2480d19b99e92520c5081df67eb53d7cb4afd4e1011af3420eabc1efdf1e6ef6ffb1161fe2b523887729840149d27dec84711c5542cf530c9c792331353bf046145ee41591adb4cebfc197b55cad207ee123bf01f928e31d2f063ecac733dc84341b0a5d104edbee9b20de985067d098a74cd02202c7ef0b37f66a61f029ac182f94f3c69f0687028c43a9704c216d82822b4de17af5a19e9937417c98497635c840002b0471a0a32b56910183c1168ce5ca30f58ae335c9384ad07238e41ee70824cbc8dccf1dbb7389b86fa720bedb35c874de6b55b7486e798653d3dc394d3ab2a6546b1e0a03256cc9b80aaa79b63f13e7add14310f777232c442c57c442ce546d449f627855bfdf2b9998e15a5b28257c04c616fa8238caa610027c5d889d2ce3d55644d06f12d9f799c7d4246e4f5e10ff41d5c7235106e993ca0140da254b036ca422a3b2494364643b0b3141fa9caa30f77068b3e4899d3b90876fa5bbafdaab07968b36ddcadc961017e1a37031031c0e35ce1e26630c54a9dbeab97dfe0cea9b334cd834d5be1eb0e2606244cace2ffea67bbc3b86f8c8894553b52a86b234155deb24ed2d82ff296878f26e0e35103e1b9ad0b072f39a97ba425118aa5ba5a9fa0775cd96de29323a5baeca4dcd320fba952f84a5fd0417385ca28f8a01edfdaf250a07cfdb9508cea29bbedf26512cdf41a12392a8e461ee36fe05a3485c635cd0d40c4955242a23c7ee02b40b4d7fa14fcbcfe2f566f5981c29c458b94adcb7d513c66e2e4be5e124f04e263d6dd6aa7ddc7b61cecbb9eafadba8aff0e8be3efa82f5063ecf2b861de814941237db554f5545979900cdb2633cdeb08bc4358e04e8494d11bd4a7ef3fa6317529923e991c4bc80cace4fbb1d376e170da9f46930062986ed050adafc2ead92c96b281bd7a834e3f827e7f521c4ba88f12c5d26bf368c748bd00a31527416ff02929ba3b1468215813e5c56e67565ce8f5c792b5aa09d1446fa66f58238b740a30e26c701d91e056c08283cce61db4381a0b1e60469ed7d00b65ea22dcc027afebffe8746f9076ec959e38b1f2818a3b1e3c5ea9fb4bc370d15b5976f002c88232c20f3adbd272b604da91722e668d5636d57e5964f4792b12378b0d670b06fec4b72c83b99d3159f3062d38e6d834cd3fa09b8b688dddbfc24d926cd08aaace802859631c43118972e16b708b02f4397b8501333b8374d07b6175622f3798e32e3b70583f51930e3498e7529292026a419acc60a8d8e17bd6eb1e90a6089580e6f4d4371f796749193112e40a10b8df3027e233dd01a4b66b0c572e7be9b797f8d65e7f877d8a688d157de3f36d85a29e19cc64986ebdbcb55008eb379d176d757bf528270c0f6b41e9edaeca341b7e68a0f74ae0e05ce502af5c275a56d5cc5fb8b77cdc87aa4b61c586a985fddb29e0455453143e934363d9fab03a74a80bd31f09f04b37960fa05ee8bc00df1cdf2c40fffcd5812d438dabb2956b4b53851298f333d9ce99aed2ea2f262405b6473b0510d5e4fce875d01a074501daa94c57b91828ea22b4589734654ec46097f053f2bceddee82e278ef26b28a86391ffb9bd5750842a00d51b6d00408a8c850ace1357e42727ef345a66a65198db9fc25382192387caa0912c097ae923e4642c4e6af56459f017ba28708587585c7786cd2e504d7e1a8f925e28032992426c8d47514d0bc07334b64ac929d19fda5aa3617f1fa55422e772b47b234525767a0d26187033e38f96187b87b7446700f3ab69fb70197c2c36b65d86e87e491c951ad777968685c155194bac29cd67d01725d63bfda30f9c69ee2b05432d54aab1f0ed0c61b2676ccd117035ac19c99ad028aabc5920fb6f3d223a1dc1ee6edf1bfd99745c4291ceaf028ab56e183c04064c56a3edbd2dc607c6fc70bed93c4963c1374762847892dc3999434e4b7049dabcacf09beb670c36d09076e6b66496237b9caf248e72556007cbaadc408bac478814e799ed7cb285f849f9d2e7c7e75415df2e5f42465d2b294f7d32671872a2c21c1f4e502057e73d56319c41fa6eb7f6905b38853e3458282897e15e989461722c9ef35f7182be46d04f8f8a9b72513e71d684412bc591e673c08ebd5346228a140641453348d3182278e7cc65392f65405b96364c2b83cf998e25d1512e239e1a7e23abea869217ec0560f1869f1ae2a27b1786fdd511f1bbe633882542f2959b17a1e031bc00bbfc4500035324f62987b05ddccab55dce6ca37e2342a6c97a3d2bffb3f58d70011bc731480110f9ac379eefa500609f59ccbbc195d917a20a48c83ee2878fb8c2cf7a3c769d6495e030841538777972f57b90831b3c33bcfc2b6ab22038618d90e318d7c01b673188aa5320a8a7719635f5609f7b0b626426d88ee2d49f326850094a875a199ef345d78239ce12aadcc5079dd8a2a0d7a1f1bbdfbb0d84828e2904be96a396375de4a58170a03a5fa6bf4c11b4fc6bc99e8208ae96273b5b58bdf5c977e6d833f1aeb6866e3b1c923831339f76e673eb9a1dd8a36b2089201ad8c4cbcae92232474673165efd71f534250f3474c980d93ee909393b8513bb17eca849fe41a0f153cd62856f5de9a2d98c49423481809b464f6681d39925e9372ab55afb11acc981055f272f8c0eb96e3d89b1fb302c0616970e0abd58bfc974b98692f5ca65bf64be4c082b3ac19f8bf9f87e3f08983d38af6c7597cba8865b21d68ff30d97c34c9c8e957a7e9df6b8bbb24eb764b9b452033aa330ae20dce07882c5ab53f0e5e8a0807fa97162e9965b25460c734f2c9f89fea5df8a84e53fd80d1f8328add271227328f8c5a63f6882b2e1ed17113f3a6513dcceededc2e9a11df1c4170ff9fd4be0cf0e99e18f08cb86421d2927c7646d8fe5d2fd396a8c070556288d669b1e1b51e428c56f5820579f0e8826210422035239a080a1a7fe893e9165ac07fcadb8fe6f96bf6064243061f0a8a8822d33cbee804f313915f4903874032815a2d49d8d2a6e35a01d72687cda576c9b8ed5b190d8be83c66e2dc187bc523f64041e93313a5c76e16fb4d8f4291e33a15c626a2cc719c6cc8f407ec9dcb9de7f7603f84f9414a3af2d481cb130ca41d3810b530eec52dc5220e2631c231330903b987a58333cab9fd48186efe058fbac2e327a3b092308e9878d6778cfa8d315834729a3d4fc4f93579c7a80b80234c5b57d16ff568f2ce3eaa1f6bf8aa53a3912984f9339d77002a3af52ff2c8e16ca0b7609fb5f8d566b8aca477c51866630f0a66e4226e6730d1a5b530638abdf3398513a219edbc19ccb5d08cc3f9addb9a37927e9d6120c1ff34d97b8da4985923405b5706dabe87080dd421cace11508e9f85be451ac5a07663c18197ff25d46400cfaea22a3cfaa61dca7bd48e50071cebc8df346f94eeb2941e7c4a99eaf1413160ab49de76c8f41685ec34b2b85694e03d6893f4ab0bf8952306041d7414ba52288220855b1a17eea810fbcc9cf9e32fa51231661b798926c2746d4c6a6941d8164df386601b3a615fe30ab6384859412afef5c3b319f8f3731e6551c7f5a371396f6038b4b554881cfb3eb8cc3b4c32e72aaeb6f1f1cffda861df90417612933c1b6a1fb8072d83b8cf64d6ab61395805392ba4111cdbee60b591dab0a0fe637af4017fe005331c861343c9b03ea749e15bcab7a79d2fd800a73726fca4d0042020bd35e01b097199b0810f8ea5ef28fc7c15a6b6daf521e612bb8f52963dc240ac60f94cfcfa269d887e2dfae7b1697b2e594b18a6cc31c8ef75f3f3361fb2eaee8e2df6af8fb0e67fcf4c867e51a47d72d6043c38dbd28f9ab520986d93fa8a16af7cb548309c55e237195f1221cfbb70229c87534112d98f060651b09b5c70bab5dbc1621a1cf49904590081715412cac94363db6a0e19c0d67269ee5c3580fb7f0016548d2c5ef7309cf28e7b3fdcd7a54cf448d0070a4010f590815c846c50b02a07e006fa40fed32d680ef295b40898d899f4534a9afc1889a87cd94a59ec6c48defef385eda0cd850a6398c13d8a8a3cff1d4f414a5afaa31efdaafd27072f9f456f9f1102830932522f5f992c8925cf4763ad00a042905a377a21fe01503f30a87aad66c55a542eeac275eb23a355acadab3ee02b022ec0937a3d062854a26b57fd65b6b9a7935d4910865b7e09c9a81b729614555a1f97d8fe82981646563a39e6f9f479a898938411bd861bc5ccdd19a60ec8c6859235da5528e5361c20818446a3c4f2176c219669721641c22db50a0f6883b5d82d33f0ef4c73d96d7562e6e022af81a15aea512fb442c5140903b7e24864c8f6554bcca8df495b04accce70cdbe3dc6b025e2152071f03795876d1032040aa3a9da202f92f18af9ecd7986f0bac82027ada1f36483a9dc6fbb52dc2e4c4334af01d247e28b136bbf2b07e19ad315ca9d7eec4c07443335b8b10a60ff58b70f8de156e8464eef41244fabc00bd296a8f8f0745df93a7f3370bb407c1a9bae4d2e32b3c75e1afb5be2ab6d3e7c6c3a0dba0ec0dbe1f7706139d308563c641eac5b7db902692292def26fe1380c63e566322fbf65ae22906223a0c390b03518043e943c46e209b836dcb7edd50292a398251976b96414f731c584624043d116a7f8075c9e4791df164266e9b6492e58564803b215b207e7f3a374c103fd867ec654941ab4136f886672906a0c61fdf7f576858d20303dd7e014ad60d576565c41c7ac9b7335bb42c2e3d4fbcbcaf349c053ddae0367570625e4839fba2174095b0c968b6ffa1e84f32f0fdac4d788115fc2e1b228ffb4fc676521f09d2a810d8366e97aeb04c02779e94c0bb920849a3d9bb06d47615885084eade4677bf7e033e4f0be5f5c14e3d3c28d236071cc3ce8d343826c38305432455eb2129d98b3e6044678d3b5c3f40a5e0a03c52ff2b206b3365dc9b8196b18660c71936c606cb99efa3da9363027867ba5738f7afedea2ec4b0c9f060102bc965a0fbd31efe643954629ed127d9c2f56e014718b9599e718fead709a3f458723064797d16ae9442f4706d83b7ca4443690b2ab2b9fcf6b047812e32803db2b157b4fb6f6106483b8229689d48624d179df3890aa1d5c8d6483d3ffae285e4358e03c8d70160f2f0b4aa255ae68f2b82c1c61746c6fb83af6a1b5e440e2e8588c89e3fd54b5e245339256b5d4f35623319491cb386e9d16993c062cc221898c890fc8b6d454805fff18ecb26eb17c0d0bf0ead937da0867b125781a299ec8ccb258027b38f72bf8ceeb678c3e99040642c28e9658e77bf668a06de491c2a4319281d881bbd944faf1ecca7fd9591a18ee05c4a1b86d2e0dba8da107387bee066275a64bc15ebd20b4db1aa9710cd4cd108c318a71ff313527594f26638f9c6b631ab5eedfa9da00d267f34040c1e1d8b26599f26235d7d126c73316d0107beea2313041bf4b3f588ccbf91bf8fadb67cfd6e6f3017c8bed19aa0df40f24613d825d4a481e4e6dbd6f48ba9257bb6f3f215978506115c7213602a89be197a3608c680b8cd9114e0f1eb25433dcd4a1f4daec3cd39a83f62edaee450cbe1284b003172e9e2e1d7ad854be60b6865f4a8335d70381196739ad15105f2106467c1212237a77b49a9805d1b6ddb0f6d2fe9ce621a985085c71df3b4357dba226daa480402d23d4028c0b48581c7c1da45807d205067d3eb100bff2dd26e10fee1b04966411174f0f6e0d8d3be376d27b913228c2b6aec48e7cc210e4b0ae35f9a429de563de2a371fb93a4f36824a437045cafc52532728971ec5bb461c281e87cb8f56dc3e2079129624d69ae525ab9c3953ee7c56422d6645df32a4b562bf1c84da68d61ef3f772c7ac71860af2d15f13b160c39069c0164f403ca58bfaa59200ebb4a070d854c341707bf3a0476b5796faf4c957e46bd2c81b55a7348281486808004fb94e489a36ac8ea073317737060407001e4c75a64ce15b2643d8560ad8cc6d7c1d3ba7d7f8a0917ae6d92ecbdf7de72cb2d654a29f40a7f0ac00a2015f087cb9fb242e5f25beb71387a96cb6f73e566dedc48ef1b8f4aa3262a8d37712228252f5f94bc302d2135947ed251a0f493a6d246ee74e6f2cb27a7345045f87dec71f9facc91bdd049a9f4a6aee7efdaa59c33cba836354d7a6b33a354d36addb8c971d29bd3eab6715cd779dffc3ee9fd719de77d1f08a6ecb4567adb0f4ca5acb5b1b9c1993838d21bc77a6de3f5cd8dd738385ee7e8dc98376e48ef1b3a1e73385656a93cc681c3e30c5673b592deab71de9837bc0e31f880f4ad18781daa3e202aaf431c1f90be3683994106d23b830aae846c2daa2ab812526fbf07ae2f398bdf86e715eee1f73e40131db7cb99a5681256fb80b4284ed66a057d098ab3b8dbbddb5dca39b38ca5797487caba1cd7759e67c1f03eefb35eed3ceff3e814ebff7d3f3611f4c0efe7c60bebe29893ca49a97c80983b3936366de3efd7c591021e9d62476b7fde6cb1ac29964eb1f23b1b9b023fafb399d2c6c6c6c69b6f80e3d055a5ae8ba305eefc6ecf91f2fbc9e8a4d2296dce001c872e3707f2b9675e7fff9204c08cabe41bd25d173fedba3802b95344ba19a7859d53497675d2b0d2c515dfbe610cc1658730a0d8e88188024e62928a5480820a48806b74d658777216eb185df247fe4cc7ece12fe6cfc419faf838ab7538ab878fb35a8a0be01e8eb253c7ad97e7f5d15f87b37a38ab75dcf67156ff95610f7f4d1d3efef267e90be01ef6f1bb2e1ef1ae55715541c73cfccc8c81aeeb666662ffb6b06861e207326882f8828408081f52e045882dd488f55bd5e46f76efb68bec2c74df6ffb55fddc5f3980e38965e9d275d670771108265d762872820c199270c01145325c41c512359c11ebe7da51fe61769977f0ab9f99d9ddfd9bba39e79cd9f7cda6db40d92b52f07084093f7881e202505030e60a28b0b800c40a9888410a1a4660e840868816581441b4469a24317d458e6b8575588e58e7b83edc63a208ca042e4e986082bb9cf054a4a4e54c132c5c49b2e79cb22b7bfd9eb31c88dfeccfb0e3bc4164bdd79cdebce3bc9e4468beea9c72877b5aaef866e3bce211ef0edda1ebc35d82f1d96d7ecd9ffee26ac8cdae0f7f410c931ccad860440ace380ad3c40a5a90c1840f42c4867a70e10ba32d36ac31861633361821260b2753d2f0428cdbf7f6edaeafdfe61dfcce6de3efd91523a0cc602106349e865290429636722843ca1a137e8cb37347040f4bbab8029acf80dbba50782882892f653091c5286a0a1dc6b0c265053e1c81011563c498018a2e3448f1812515144da9c2044669486f2e64b3381ef158e4b9f1c4f6f77c57cd1c99f8b77fea44d9912747f338cba794f31ef1ee5013261d6848c114470c710293c1e54b1334184d41c529d6cf551e75ccd3fc95c753c2fe2500171b7a78e2046c6c299259a28e30a3ccd30aca88f51366cafc40f4a4030b530c4921a20541b46102349cd6b81acaf59c55a3d8e7e7b7ab7e8eb9aa6c15f112a60127317090018c333ea010630e54e1850d297a688309333166772539ce923b3b3b3b3b3b3a5c4546002222e4afd155640420222ebbe30f00867db0b4b37377549d976559662793e1eebc23abf117da31bae38ed1d5806a48c48b52c12c1da151810b2840c164e8072e556ce9a10a26c258112b0265863154ce6001130f212843430c4b616c31521303025e1781b8a705bbc9dbf137ba3f7d977559d52c32021091cd4866f5b6d0318f55a9542a22413749e49092994408f2978a7be45315ef002f1167c917410c52c18e2ea65aad6dd7e59efbcd05cb3df72e56eefa11d7a53dfe3bceca4256b97c0471f964485d96b97c7cf3dbb9c746b297afc9cf5ebecc6ec636d8be3f0427bb1d7f43b22eab95523a660f821ebaf23329ab6ca227662d3d16ace4ff0d57aeebf5f029b0fc81d274fdbb2ed500cb496b7cb9f3adacd2325b9674b9ac7cc25048fce57da595fa4a6ba962f3465625556f55b55ad6ed9c6f177cb99ce48222035c4e5a43cced2b1276e33de77daca3f56f2309bb21ead48a63add18f6fc8f7f5fb7bf99d5c795f7f3524f5fd3d849deeacd55a07571fd8fccd8760f33722cb10703e4764d9dafcedcf1f5d362f5bfd40d265b77d29012b66f6ed77e10afcbedd77a970e589ab0f6edee62b7023da88df5780656b7b96adf1db7bfadf77d2b32a30565e12a6f6d21bebddc223ae3b5f86556cc4bee22d7f9ff5b284d5987fed2bcef2f77f7f96bf557553cabf2d9dff0d971ccfdafefd257db80a8cd5eee8d5a7a111edeb775a3836905e732fc73d10be2f8eda8360e866af65997596ffe0f2e11fc4dfe5ef8345d7b6efc8606ebf07bca36f7f777b27e9eb1961fb880e4e3441a191018d18b13640165b9068a38931a06c89757b0d1df3743e495811fc256d22d04395ed3751255bf23b1ede01def75726e517912df9dcfb11d735a28dae1e96bef6fdfd707533d8be54ca1d67491e062f0ff7c8ef54602ce79f04d8e9665f447eadf2816e75ad2ab0bd2756807b962d1f81ae87e35f1edee12f7f03865d805bf2b71fe075b1480bb684a3a0eeb92fc1fbed4be89efbcd055bc251fd12b6f7be04eebbefb7b923cf95bf12a2bdbf8be3caf2cdf1a2d80c9861060ca71bc2e8c2892464f8d0822acc302d114505c6da957f08db7b1f02f7ddcac50fb6f73e687befab18c47df7fea240d788ffe8f242962d1f59ab943e2a3076de91c79fba1f716de10cab6caf92b04df4a957256135267d9c25a5b07f479f2b1f89fc25f28bc81f413e11756f86b6f62bbff8f592dfed815735856dde015ee76f889db307fb77fceb7f5d129643cc1047c860c50e658089f9fb48581024d4b060ca17596a7012f37f091bc2b266892a68b688716223e663a0c0e4841067ac60068a983ff3380d5b144a98c9628d14b4e00aae05556650a20b169e28c302cf0a1e96ce4c69e2a40b00a0008616393889a1864919287e60618736d04065e113830926a0619283172a8491c60b60b4f8e084082c47b1dec2883164c658628c1250b019629c008523c0c0e26486a1262e1091260a2598a26078ea98877ac837c9103944def92e92903d091cf42b515d1edcd343e49a350c6056cf24728d1a09f41a2992c000eee99720f02b9b279844de4cf4e19efeeccacfaee46c22e0431467f1e59bddd1e7f6b74f8f97301e1386090f6984185383090358c1531037a031c40a44c4faad84752b50b106982d699c9888e150912445114614356d8921258101e572d2175f2e079793be88728bf8cbbf896ef703054120c88eb3da593e3d46289201671571560674ceb047fc357253910c38ac89624448b016c15f3e1256631fd4111647acc0888828d6cc4caa7641c9263ae619dd480b3328db2aeefe406ed36aed544dd9a46a4adcf1d7cdf33f8fcf73009ef983f82b7cfe0efca5c1f303f92be7f983fce53d3f117f89cf2f824753cfcf92b00d9ec3adb590e3d0749d7b7cb9d0af0e1b3d314ad9017c5f85005f85f55d7d2ed4fec32a03f81a5611c07b6115d6876115027c0004f022eb3520806c5107c7125c6dbb1f1bf57ec13df4e31efa3e9cae61854f53bbfce57c614e8e1a04089bbc8fe2ac61cf2cfda080a652a91450d052e63e3d7cb22c48074a3e7d40006983b0493ff90236789eb049d1c73610fb8cb7e8a3f031f6058c3937a5a552a954910c34655e6ade8ecaef27c5cac1aaf4fb49111121ccb66da9d4a73efb7e522e1dae0184ede42d2b4ad169244aa0a8ec543baaec1be1c8d3b681cff3365f37759e4e1335a1e057bf9be695765db64488a3346ddbc4310417ac5ae77dd3e6584debbaf013b51dd0b09c94a6e87217276a6edfae6ab3edf7d9f7f37511edc05abbad829be7d5ba6d75dbb6ad82bf816037659639a87da236e57a11efe0e8a7756f5b1269d39dfa40ae6dba33ef0b37ea7d9e5f1fdef9f06d7b0eb53970b7ccac82fe6e7cb0fdfc364eb6eb9a43d2d24eaf1471b38596b12aba8375d1dbaa46bd4e7ab6724f96514dab35b56dfc1cc762eea647330d1c876ed3ef07070d7664a6cbdf2129996f7370dc603baebc794feed16a6a9333939f4d97618a73eabc0fb4a954aa39701cf27efc56aedab6d12d9b72dbbc3f1014c710dcd651d3fdf4395dfacde6dfd031cfb85db9f5b78cbf0d880fefb8fbf17dabcf3bbee7e781f9196f5577f25625c33df54701f0f02f9803eacf63ebf2955b3f64d89cc2ad9afa8e16a19424a6c915ad89a3d4fd82790d3ee3d669e66e516961a978c2eb32d622b3af3fad4818fd3a97a4d29dd4085b7f36f9ab6ffd398679f0ad3fbdb8b5d61647952afb7eb2ad05ab8942475414da8088231788629a58b52525a218c74e906bd6c48efcc7ba794b3ff569c774c255107d9dd711851680438c8a9ffd28c3dcfa43312d5c05694f9faee48f52e9d6cfc4512e09b509842062232654026f866d0b19b123ee87621eeb187317169eb022265482ae85d891cd97808aa12576c4fd68ef0270888daa5badd8ac89bfbcafa03771abbeaff9b9e5d67162e9e1d69f4ef6d6a9464e2f7532b9c490e19c1f7d8c9b1f1dcdadce0619de64a7f44c1c55ef503829374516e7475f923937a23789dedf0f10cbd60ae7478954add8ecebd730bc83bddcfa338cbfc0af3f99e61486c9a86da630b3f854e3302039c5b88fd9935bc7ccc9ad63d6e4d699e6d67966642f3e9170f6f9e5d6efb250ca2809935f30d4ad3fce14a4995b65148ef881c7a82891b210bc3a64ec28a374c4d85122dd5a5f22f10e2e0c7389a344b2e268a9b855d4e25697225bf5a1b4401c2b2de2bbfd11d17e5a91adfafc85407f4a912d1d30ac3779930a09eb1d2dc28e538a07ddcff79bc7097338abbe874980f06539abbe904ea444d8fe29d6f725d605c51ac559f5eb07c4131d4ad8dd6ef3ba90bb53e91639cb9ddc63c2d6e7cad8d1752bd04d4842d747c23bb6af0f24eb575bbfdbae43c97e936263e5d62b24f494c23dd59bbc55bf566b55f5bf88fee4acfa9de84dceaabfe90cf1e42feebebe3b75d9f7d36d73eb365147089b89cc3dd994c2adfa5ba731d92430acdcfaeee4afefebfb99e6e15f1ffc2f742767d5ff446fe29efa43d8714a19bde9d69f526a6f534ae67455bc438584edad8a46b62b9f6b7042d7bd03bc4c7f7ef77cfa73663658a942c2f6f38f16c7bfad4da06f159dfbfa4f5b497627fd2addb38475227babc76a650d1df3b49c1ef36bcea7f5ce9ffcd2c4d5f6dabbee14579b9824c98fec6affc3ffceecfb99dc4b5693e5629128f197fcea2c96b3b2f9fd645f07435d85cbe12c774e646f217196bf13abe23aa904090d05090d31b7ce1aebaa1ebb010058e7fae7f08eee905bfcad8a80cb0cb8cccc2fdf23c2720008219c86d0410a13acd0019834a288a2018da215c498e77c17bf0ffff3b37829882e8316e4bb7c9ee5ecae3d6114d5c4dd356666ae4aae11fe239ec71f106e0e467aeb53edc7a714c64e40b174f8e8a032bd8f17a525ea5af5ac1a4e6ebf4a8da5baa3699a3656165fb23767f5799666a6892861aef633c7daed7dfbeeb5ef38864292f9e682f51e7c9bf74c1c6b4a053ed28f2c5b6bcada4fa51adf63b1474e3ce232325feba40cb30fbc07bf02de836205beafe2c89f1d715d4dd34495670e44c447902ff4eae59c7631a7b8ea1f8a69efdf50242c13572d0235994f7f28963d0d8b844864cb7f8651d7ffcb78a1a3a0f9f4858e82b29f2f7414543f7b4ddc4421966cf9ab64cb9f8a2c5b33f3af4dcf31edc7fa1cd344a5f923eb7a26aea4b8aa40f6da07cdafa2d051502694fd508c63545cb518c459ee2161dd1f89ff12ff22236421cb9694e23eaf482121e1ac7e6666e690382d482a95bfbe2ada6b91304dacb2c85f7d47f79a02ddc1aef8872e8f59d1a53bd83193f223ece7ac90b95295195dfe01506147151fc98caef4b08af69c16c969a9b7725a9cd59994db3f725a6e7f7625d39249c9aca490948238cb87b3fa419519ac46c85f4df4c174be3f4807404a96f8abfbfe2087a964abe96b7235a4bb5cb81ac23df8a068a43a8b4150ac5e379dc5f6e63cfb6b2524f5e0a7721e14c7e92cfba038e6dc9c21e0a7fe26ec409cf71b9cbf1157436edee681dc88ecac1c1c71041f047d6fbebecd0f797f137a0ffa9096bf1f6b82ede12f10fc54d8371506e90b7e90bfc06ff0035aa2a48320a10f6f0579ab5febc167ae4b8aa38bde15fb77ecd1df50fa9ba88ba4e4b8a606d267affd50acd237c1506c0ec5561fd0d73e88ca17a241f33351688a2b176bcc7a39d85792f28235da4041164659c486887848811921b224c184536cc8042f94d9e2c415252992621c00f103104d5411c415505c4bc79e0a96bb9cf0e58e6da5caed1f9b876b45ca652e97bb9c204667cd3bab7f70565f11db8ab3fa690d76fc3b4295544a747e1f92430f3be0738d70473f20dc774fbf1f557807f7fd3bbcc36f7ff3c03b3a7147ec197672b0da2be11de0f71791b020b2d5db8f1a70bd1dd7171473b88739c8d673f9eb0697ff131f0097991395b670d4e08e7f55771c198d3ad6233eccc57e7b69628b0d39643105991362fd4b244cc88421cad042c309515ec4fa8b8c801446e70da5a388fd445bc212751192251df3f72161d55a95ea5721cc12e44fb1869095303f13637fddfbfd3b96301fb2e5efe33efcc4aa56f5d7592070cc47b6fc93ec7b0c4434f1c50b2cb0c812731aa6f80206335c30e386a898bbbfb4dcd352daa71a1d4be9cf4e04e846894662e425207f7155498c264a65ce5cf948fcc54a66a25cf94a263bc06e5d985cb91bd492ef7b207f7d747bf92ba0d4af40e058ea5721dca4fe266ca2b0e807a809544bfe976139063e41c3d10bb05021228addbccd1f05d94f7d4a143a5a7d90fa9b0f4afd8d2874e42fc4b2c5b2957a1b51a8ca96fc94c8922df95654712cf5634d892b7f8ead5cf4215bd243026a0053c4d0850f4cccc01245d10d5ec4a861c6099517beafa1f7fd5eff11ef722010bd711c5b84ebfefe2c85eef61e8e39eed75bfd8dfb8e535d77e77e86494ad071ffd1efe7cb66c646fa73ef0e866e67f5b9a7b5bb5962842ca8a0200a12555061fa20ea09852f6b9ce1c428261fc9922020ffee95ccf75013a269e2c83f24648aabf9435da7e4b6904fc8f6b79558099b281472966424ce923fafffb6f2f7ae406dc08a35a420838b0b3eb080d872020e2d7469810531f940446818e42c2253ecf338edd005140a498459212687ae88b182c60b0c51b82813938f44fe12f959c88ee4b9cd109d13e4f63395828ff8642209f31ef1b93dbfe8d2e4ccdd71b94b932bb74b31ad3a6b2ca53cd8197a5f6fd4d8f188cfbfcb5f46b497af7d36eafc30f78c39b78a23fdf997fad41c777f2b84fdebf2f7f17f7f5697410b96fa7c2cd68eb32c9bef72183771858888c8849808683c39492335c47021a1a1e58eaca446989bdbfc74fbb34939298d283051502e274531f198e3f69aa14bc51590bef43535567adfe228443dfe79c4aba27d95304d54cd5b256cc6faad4c42c240a043992bac68c38631bc88f50791300f629042862d9ec0d2248d58bfcba789a853376baddcc3c373831d5d5787bfa6e7d3aea3f6c840af4e88a185cc9a32606ce8e1c109263544d5e082342ee8ba145387b9fccee4622ebf0ce34c4e4d33a35a9db54aef4ab5bad58debbce979d2dbab1e33d779df123841507a83489cc7ecf107a6bc30cccce28459fe71de76b08b1f1cb83219f344ca1427f0e1288a98db4d517e58ea98a7bb87b3fc5b87b35c477f8ebff8b67875601c34d8295556ecbc2efb25ef88a12bc2aa7edadd5d4a972e59d765ed6e76253a6686751df30e7b97266a722e27a14165877bb8dfd61d156ce39e137776444a67e634ebec6624c8329a4d1e874180bdb097072c89d0052888c803267353189df4727887f6fc0be01df279a9c76e70017059876139f2bbd58fbefd7e9b7bb297dc0e5ef6913022b2e55fabb5619e8854a9ace5f04e0b5cf91d5e39998a3b0ef3e1c9f1196ac366c05f23af21224291a13a99355b8a53868a050e5d544183491042483124c39a2554f0840937b4b0020d2095adc5d47ab90b13a43baab4321db775b1f6f22bc3e66736f5b2c54fc35a67c65ea6933f76bff7339fa71453612c3f773929cd1177acb77938111b779c38f41593067b73b98b0962f0889793bcb861cdbc3eb38adb2dc597fa6b7fc7d4803bf28c3996a4944ccf239f41c01d394d13a63bb6932ceed84fae7c1e3bca954dc50562d810828d24aefc5703c395cf5233c595ef7ac1942bdfc7c70bceb8f283105df9406d185df9448a0c71e58fd086d2958fc4c995bf24094e5ac3c56806ee4274e6caef6282972b5b7646c5de5c864d2f52b444714516221ad2888db60bd4931bd82033861660625c6401f6acd7f3972482260a13d0f090851461c4180d355d9e9e0ca1431719625b8cd14410f91dcc98f434303ced161df38cb2e9caf9d9d3e729ae3ef0ef0f2113d9c5ecfd331a7648bf9fbe4b5e915724928ee6d12f9f25e5952bdfc3ac5640da83c0314dd4be8a26ba644bca7731cda3af7c69c59596240d5d8b235df9b2c95d22d131cfa819ddfadf3fc7aa84ad28f507477b6f9effdae7d10586f67bf0bf1fb3a8261c1c0a459b6e7d5a84f320700c470ca10a8e289ba893e611c65f4d8940ffe8cb7074a25b6fc297ad6ac5e657f48762b2295c511148b638308e88621c3bea1fffda2f225bf5e5832f7414f4bd7ca1a3a0d47fefa2d05190bfad424741e0bb28e40314a528249412e5d2734cfee88a4971459f63489fb8f278c945f9df87003e159138abfe12d9aa552e3194841971610a1cd074a146cc12b1faf249c24430630c2b6552f0031932b1fa72a93e1876912c025fcaa5a286225bf5bfb0b5855d057defffbdd7fab2298bcaa26891746a1e57fcd5b00634418ad59761780aa318c7388bbaf55b8bbf562070ac49954bb2557fb5024a3d081c0b21f55552a26c92adfa52cc184f886efd46a279f4adef45feb2d257248c63f5003f24858114e3d8d00a64949a94c048f9c2ec49b6ea268e19d4adaf03c68e72897e4c33d28632ef54d0d4df8de0ac2e525f7b0f41f0582bc9563fad61fd3043b1ae863464a495fff6433121a5958bab0fe8d70fa25fdf5d0caabf89422f5bf285e4506c28d6b5f703a4922df956d829dac438266bada1e51e4941d001c37293bbd4b274fd253373376b9ae4dadddd4d3d244a96083591c3367b25473979ab53b0939dba2905dde474c73e13d54efdd44d59946d5f9a5f7e9cd399db61f09991e066ef61f60999d74826afe6cd9a04adbc43b4db0c77293f27f3cc85861db3ed3331c7b91f354df4e7b699b9f3dcd6b215cf9869afcd6ce8be125df59757667172add3fb6f10d99ece92fea31c9aed5a7c136378803082034a6d44e1841429baa8428b3394a0550431c41254c0a892451a7e33c6eeec6c5ed871c73fc7ceabccb8a2b47065cc142640a920893066727628d1e48b35463de420048e2776545de7f1a9b386e561bd2375432e6031c30950a470c59a206a165060bc2ac61817b8b8e00c1064d8b8c08e39b029868e2f551ca1050b261aca600105e566c8072f30a421c411485bb86881f252460a231b8ed81883af28638d4d182bbca026892eca90c1640b194b50d1040e53ba582385d8a83a018c262cd8b22685a32315130268262c278049586078160021c2e2a4cb18589a60c8524316224cd8020d259ec034beb6c2f53baacad3a3ea7a17266c28e07217264a17bcdc85899a2b664e3592c052830bc2a862c4155dc5192b727c8a0c4c3c5101c2c9c9f3b225ec9d97256cc6dcdd0b7771a609c76082a0128399145660030b331568a44030634a0e3d8839a18ca3293bb8f1c48e7f5dcaf937470f155f1d3f19a0c0f2f86c89a53a8b4160e274051565b068028d589120971f44c26a2f5d993486962baf648a820a1a92b8d822871762521c8ff0d395520363a594b04dd454db268e55cca1e2583371b4538c4999e3b08ef94b1f7a8db5b99c244697c9458c21be8881025781ed2e278901450ca3a79aaa1da735ceedb756d8b1be8be7fa38ab7538cbc98516be368c526b5ef09a6801cfc8d059f9ba70813b61c9b38285cd84a62e8aa21a86eb52a4dd17346d5764a0562a191c66c898446964548861de3544344ac6889ab2ea2a038357db366682d1fc7eaa4bd6302f344f133e6d28c916d70a8612acfd3d87714ce5ffbc83e7b2b336916fec8428d9e25b6bbdd561b349d3b890c75bd6abd5aacc68a918aca05e8d25c35cdeeaa7edb76dacb0af3ec27373883c2c2b367539890ba32bb9e082c855d822ea4a186ccee5a42d9caa992bbd853bdeeaf750d54f2cf0e15c31b89cc44515775e4e7a02e38eaa4b44d74d79e305dbcf9476925e33278bb9b3c79c734ee9934d4a754c4ae99c3367ba171e0fd9fde7289f4e7166c059d23bc5cc2ca59472642e32ec5a552a7f76e7e9578a6392ebb46dbe7463872b21248097ff0097c5ee39a7bbbf9340c701dc164958b992ecb2b37c74f755773334020279ab8820fbedf217348988827a0631522c1359b684f4cb1d7a495092adfe6e2b21fd3a42588f84799564b7de1ece926c74b265cb9e73ce29e98f926546bfe953d621a564b94a25db480665d87ecdc7a505f1ed8bed8b0cca701e9da339e06f297586b0fd45feca39e94f0dae115a65d8e3d09552ece19f0bdabf9bd997f22c4ead37268ea1db0160a581a45c9dbcc4630e2a2c97018e1d1c9f4a85c10d15007e6addf799dc68e53c1fdcf277a48c0adbd11b18508a73a3f3fda0322727e7b3d6a66c3490329433a39b56b917e44f77647f64df9afa545a2cb783dabf2af7fd5411fc7e480878decc9e0e913f5ffb869470b3ebd788177e3f529c99a4731ed99f19422048d6c44e91fe643fa668a14caa55ee9983506de3bcef2703e79c93665ea7cdae4baec63c853c6d529effcd4b7ddba45ae53acfc8b79915f9bb6d5cd576542a9af252b4083b81708cb2ce386fdb7e6edfddf6b3cbbe736edbc2203a644c5e823bdf6f673d39676773f3fdd0f74f0b62fad0c17a481b934d0fa57ae8b8dba54baa75b5f33e6ef2ac55e51879729867f5ac8eec328bc8cd26b3ea5637715cc255e8d28fe7fbfebd94f7ac202e7fcdccfb36cdc572ee3b1189e3fcd5fdedbedb6edd3ceffbb84ef43a6e87fb54aacda6080b3eff4e8ee6215f5e7e9ba3e261a5c22021cbd60f900fd9c27181fe54edfba161c6acba8d21b8cdacfadb27645e2e9b24d8bee3b81dac9506f97ea838214073e8676d3307b284fb09c1f170be1ffa3dfc452f7f97437bf8e01e2c76721304421fcef2cfa8582ff31efea2b31d5461de56dd795d6de5d6dba598cced05dc1cae7e1f27b75f0bb8445c04873911111191512c7b244c6017f593feb1a3d8da3c7254133dd97079fc68f5c54ed39bd9d7ec6d992e377b2ee32753cd69a5fc92e3ca9ff22b0df255465c19861a57b2a0cb954e4857ba18e65f19c6316f2b77486bcac4f4d78ae5ffb49793c0d052dd8b62bf9fd56ffb553da5eb523e3dea9ce04ac8bc4da75899bac1923dd9d1cb022ea401c3a94932dd5103eb2ca5b4f9c27a51a8e0e58eecc5f539b51d7fb158ff2a95b539ceda496335202a71c759ae627d2078cc7bfe3ee89827be6cb16047af5aca2a6d7e3f5db78629d5348d366d5fd8e9b02647f351f8187d7ffeb15d16ab11cc0818e7cb21b6c99cc1523ef1c4134f3cf1c419fce59061fbd9fe30439d0184a73a07caf673b7f79037081a4afa4301050e282c7f430d99fe72470af91b8a77709fc1527a0ad08f4c058e282c83b95324a17fdc681c5158fef6261d27c0ce577f544affbecdb429eda694d226774c5dff7ee0607946479f551a779f7d27c1d46ca7dadd9a1c55fba07d143e067ed75e461f0c3966438e75d3ab927a302cfa18f8366c82c2c740b1e86329b1997eda9cfd75afb64ab3399b9bdcc7715fbfede3ef27497d207c6596651909db6ba1f69fec4f9361d6029bae1e45a3bd9452ce4ca3b56d4ef5b23087b392d278b953279be1f37ce735f7292f0b691adff7c335fa1d8ecdb229bb7e43705cca79ed79ddd3fb3cc8424a43ed046b80cb4a667eb8a9d0003130e418e82c172bc70b59f6158089fd6698dd4c74e5e0eef6fdf0cba2c2d66fcdcee9c3c16f088e3ba5f77990515a6f789a9d2f2595947e3f5f0b571fb755a9d1ec33cda314030cbaaefbaac1f7d3519ae58eaca6f4334d6a9a465fa35a6b54f47c356674f13361f9fb01bfd8ea3efe1a59294817547a090b304bd55c5645332d6b4dd3fa332b612ed9f2afd5950939cbbf899ce5fd852e5756e50bc1639fb884be8088848da05bfe4caeafd031503444b084978082323198eed83ad4ad3bd71943c43bda033fcfab5de2a5ef477999f200790794e5660afcb8eee66aad75e3c24efd16fed4e742205da83d90842d898ae2a85a6b98b560732f057e32ab9cc769d9e7814d6bc1cd05f7d0bfb1af7afbf61bcc62fbab16de2c91b086c20c258aa144759c4d779ec79f90ae6d3cbb75f5e2789ca6d5dad5aedbbcfaab54fd15587ff5556eebbaaeebb6ee6fd8685da55dcd40fbd1bada69936f7c38f81b82430332c6497f3fbc86da65316a2eeb721217686e77c3637fd9fcd675dbd66de1ca03bf6ddb58ec529b4d286f7f40988dab697ebb54f7a9c709fb723a9ff339fed211c7a1aba393921f10e622f3329cefa6970abbeff8fbc16c5c4db2ac5d6bede6496daf13e6b09094e7ac1eaadf0ff79cbaf1fafbd11ae7d3c46c0cdbdfe9785c57bfa31e4ec8979de56915075b5f7bef93f44dbd114dcc7156d32bac36cdc0e1d5ef47c3c0ebba59b31982e3500a343a6937a5dd1b80f55321dfda81d65f5e576d1a9acdf707e40753976e0a23c60b63e3cd9cecc67b2f057e36bfbdcd8d4d2a95e36d9d8e8d77b7e79efb9efbbcd4d71008ff68df75e1f640927ea87d7fdd6bdd8971561266d1effa2269285209efd89eaab4f416cd01fd2f1473e96fa197db5a700f942f2e7dda6ba8ceb8b4cd5cfa9a18867ba8133b72d4a5f49708f18ebea90ffc6cbc8ee98ebd46121dc065753800dcf88f195ce696ffe84ee4718f142b7ff4dc55a5f3974f7f72dd48b7da9413574232ca3b9ac7f623275df6323297cb0cb88c80cbadf0320e1bd676f93b1b41b694ea92e5302c1f17278e2e16eb5f25c58c3579b497e6c07ff49e734e8d6f966559bf16fe643f8284b5926cf92bf5936c79691e4ea539f0f771b958ac7f95ca5a2aed85fe0c333456f693844923b7aa7f16cbe5734769247b60982fc996bf8a8bddc24b58aebf349230bf225b2a2a8e2aeb578c240ccc817f0da50cccf2772acdc3bfb4520df7cb1d9dca751984aa75b8ae54e53a9411f7aca0fd288d74a4cda959289f38cbbfdbb4ac7a35d39ee739eec77fe01eff4e7e3f788c67d9abdf4fa67296bbf61dcdda3da09a26ba1567f9c7d02dff1bc2ec70bd5ba3f4e1a967b8fe7ec506ffd23c640ccd813f16a51eaefb142d4faeffe8535034240cdce3d2887bfc3515ece856acf80f15fc7e54bc4313c721693d9a8406893b4a30d75fda78a669e21882db371ef38e6e1e7cbd73b18d4ee7049667ac73ceee7d782b49988c49b9f39d15c53bba5b83612ec591646bfe7418ee7415ee7413c23471d39beeec445a245b921ac9967ca8f19f9a3acccd6022b9144991aea4e22c69c5593293c23d92165df97fb39925ea8e540bd207325689c699e5ca1fa9166ff1e7cc32b52051af644d1df3705bddb6add65a2b277f634deca6336738aa9dfaa99bce5cf9479028f1a56c4b6b5bfd5e5e137f75d93b94b3e4f793b3e4df8862475ff225287f752faf338511e34edfcbf733fef25e6a1cc7711cd1173a53f53677f70f5cebb88ee3388ee37e96f86b1ca263d8b19ffac95f7dc65f2ce42cb9840ba7b3b89f889c497ed7dfb5d65aab8f477234d1975660a574314f4c1ec697b235ecc869ae7c254b849aba7e28acb52858b9dcc5045fae3fec7217133ca962c79d162c7769a2a6dd7d1ed045852c97bb14295d035cee5214e5ee3c8fbf3458fdece507c29fa0011411111151ac8afffdec781f594d5489e34e8e673761d8692f4f86d4b54a635e55cbac852bafdbaaa6f1a77d2b21b46a5f438ddf56ed059df46a7deb2feeeb6bdbb6852bfb791de536f0eb835fbf72a9edeb6f627d6da3692c5ff6c24474557eadb3b20782c387cf6f080ef0f9f3a03ef8d5c14d1c876e15333496ab6a76bf859c27aabc5cdeb6a7f539cadadc9e7b0e579fd76d9459ab5cfdae32b76962a59968e4c11e7af28e9ea2463465e73527c65dced93de777f79c995579e14b551361b56f7154b5ecf9c939e79c73ced933f3e4f7237b6a1e75df3409d4cf83ec7dcc714d9c9ec33a0666b3cbb6aa6593e33a8edb3c3ba7cca494cd6d5593946619cd6696655fe5dc001c439089ae1ebed236ac44c1ebf7f7d3a13901d7efce19d142ff24507f7bf69e6af5db5e0bb7ef5a506bd7811ee86d42b410fcae867efbfb799e1350ff7e34277280ba5dcbb8dcea87dfefbfe7db71a779ddd3fa7d69ddf8f3a0f3eacf075e4e42f3e5d6d0efd7d19417324f6e95e0652f644c70b5af6d63b55abfe3c2cdda54d7759e57bbaec3f16ae8b776b57e97533bbf36e1f83d354dd3b82ce3c4aaad90c5bd5e55e14db1aaa9524a9ed212e1305180c344810c4f7efc90c188597c9959b3890b2873eabb29bd54ea7db8166668bc90e5ac90e7b3e227a521ba5c41eff3b80d044170d37cf8c679a9507bff52afa542ed6dc83765a5067aa067434e85148df7d9efbaf6b68ff5fd58f179a258ed4b794969a05cefb3afd355d0fb3efb5a48d3d8bedc040b3fdceefb290013db5d6e82052957bb9dc8e2be1f7ec194077ef6bbea7d077a1c0ee881decd6f7f8373e379def7799e27de78369ffdedfbd1bafadd5c3b7a37e53de7dd78a94fa552a0077aa9df3ec5e1e0d430f5a954bfeafb49712a2fc502953b72d0ecac811ee8f1e6ce6dae0c5ab82f95b36894cdd171dec1b73fcb5cd3681279352dcbb8ad769644e8ae863c7d3bdf9361953f5639c324f2ae84684f65c6f5f7e5be2118dc16c710745dd7691fe7f9f733b9a7df7329a5a6494dd33419d2efa4d675f742911377d5957674e950a5f074e5ef48cf1675d2c747c9ed9d63e69c33851f5ece3c738ccde16fd35cff1c58c7aa06f8c387c28e5306a31ea8744cb7b4454d2ac5684400000000c314002028140e080543a1603cd8d5b50714000e8d9e4466489b8903254a611832c6104280218018202000303244c206941b9faa46376312ff1a63a5c71b4fbacf63e9a9071df7b380c69b34fdd16da3b7909ede06979d6c0d6281ae85a7314e100c70c7edd1c427ae349df306b1cf8aca32ce9dba0849656973dcb9cc03e8ce604e1ba7dc5df41049232033b25b32d714dd718af9d4c2ac61a45fd4b2d23f2244047c96285ad220c6ccff90adb41309107061c5f317243a37115906b460931e54e710c50e60880b357865f246da1a901344a80ad7d0b0dab501432a9978ddc6d9162e52e09365f1810dbfd4e7704d333401fc7b7a94c2099353226ec48024d3881ceb4eeb8ad073be49281aafa0ce44f9929dc5f316fc73eb775edba78c19dbb2db8f2fc49611e316426fb0001700f684422949cf306f190c84bbc8566142827af98ea999c8c1f479a23d4f45b0147929290d4b4df0aa65fe94a531b941d0dce236274c6628e3e6d4151b9b1c1a2b1b86b020eb338df9a0be7289ce5de752fb840416b836549e33de0a036c8e58725ff7bed54d7ee02a37d7bc807f5fe36637cafab576efb40d46e6d7f9e9a7844b58edf948b0d173838bfef5d7b4b2b556fa034e70afe251bf16c5d444913aaf59f81b9ad064fccc99f70ac213294fdbd8847bde78c19a0c9d0142d53d041022a0374ad9160534cc753b33e029c14ab0499041317b0dc0c8d8990649a588ffebe3cf3011b4099ba42f04a5264958f2f082fb1ffaab260586d0d88c9e359b3d43bd2ba4cd1d142f85c757354e23e128282ccbed6cb1f47451f30b02aadef5b35a24798615558b99cdccd61534db2d00f714237ba294eb80f90677006c8741ff8a3e96f80056e61432c11877f15b0819482d145a98003fedc2acf134ad9962a5fbb77d899f845c48f8710a2b6035152370abfc199ff3c43e6bc581f5f14d8499b7de720b84056b7e6a6fca9e7ab01532ebfb033895353f3c692a263ca2e6f24baf10e6a68090654c5a044b9e4149ceeb7b436e79bb04630fc408767efea4225facd7624e0850ea779b279b52efbc0722c5fa347ad3f8dba07fb862af8612a39b7f94b34f96cd42f8beef4d6edde5a9ae163865fc81edf7bf7151530f96470f1960b6d58b02c13df9fa81cc1233050161b8896c51e2aae7c8e2bae7a8e72f1ca6ef89e8cfa8169a9b7414d5f40ea8784ef2606e4c16cbca59ab6929741e5b96b27e70f06701ab66ba40abf38fd8e47045a5fafebc80dbb461b805627274869d20c2c378428c8acacb9fb57ae4dda63c985aeeda0cd8a7da4a5b7c3e0670bbbf11c73e16d6a2631650d407edcc3547a9d6da4185631d26c937735a7532a0d9415286cf904e2c3963d25dccdc12a13a461b451a7931ab8c2ece1e4f5a5bd3258607854b969eefa3d4cd638a9aa380d3773890ca1861c419c103840d3e057cd9cb9ddf0c644ce90e170c7c3516dc73609c21290d30704ec75caf3d238a9b4f200b757274e111914d6ffbdd9e5045050baec1de7f06f5d8877e0c561ba7cd5ea379e1a04db2368b6e123977cefcc9bbcaafce1783b3c230f8131899c535ea63644cc37ffe734874d6849ff8fccacb30e5dab7c186078ef88277e0190c7e1bb0fd7c71420a3027c2f51257d14a4ee2f112b2cf61ce057fe35319557e40844d9741b9deb38bebe9c0949419581620f65eb05e2d026b6b51f8a12d01099febd25afb38b15d2c4a5792f729db721ed5a755e106671a03cadc0a2f4cc23e5686948fc58bfb44ef8720bdf95c8e3ceba67b1853f750d4db7dac1e3393daa3d51fe9659a2406ec8c3bdae3861b9dfd3c28acd484230093e2cf7eacd3ed6920e69609d11341e9eb9fb445344f114da2e70d77a40797743e96e6db611e2093bf8f7adf8b677c41d441d255ac01377146e6c52e3935b468a9ef3596154c9fc2e781164a87170010450a68a0851f3b953c49f8fa62ddcf9fb2ddeac306c7b6f3978382ef3f83b61fef9494b6befea32660e113d5d838899c51959ec64d79355aa0e1f6464d2d960536c081a58460c858e8394e258033d898cb2b9ede158e651e46ed5bf001289e80dba61149ed4408491223039689623a40dcfc16a1eccaf6b6deb207446fd3db741b7f6bb0b0786f09e4941b284544ff13f86687d48e72c51b54d544f18f89a2000a7be04d8ec2126b25b66749f709d5732809412b31231c4370d6a80dab984e5837f548ce08af858e8ba3961625ccbb990c6785498a9f7a90779ac7c43d79bafd705e9d97b944d32628c44dc0b346c1010e94daf7019e6781d60a0c87102eb56af399bb7d1c0c277b6d7a7aa6c027c99afbabf8c11b12ad567c8a47989bc54880591c1d46a18e3ccc0399a1cce64c809bf5d4de8e5b6bb3e246a052c9af69c8cd473e27f012a118dd7744ae4d915b4b23a78d149877e428977f029ce7026fad3e169243ed0bbb655f5faa911b67eb618805942e1109a5207d50718250a3db8b3f4de5e6a9e5a1da7877e552af891ac957af801cbd6d5f678baa757cda210cc8bdbb368e35266b27967c71ceae0f1c19752a3ee156b530206fdecb3e51302b03a930420e59edf84265e926db99c09880c7ee24e5f98509700d63e921e48cf5b9096d93d3c6e68d6d4db7c77cf5ed5cd8352f590b63ab37d7f820bbc4261d6856c02ced5e1c76cdedd4203e80e32ce2cea446e2d05ee8b9a72fa5d0d4e4a3cca4723fcac7c13256f4285a9600a005921a479bffb06acc5b8bd9239bcb24933a474c0ef2ec07ddb074508cc35006d1a6f924363ddafb888df5d77cb1d8e0042f40476a38281da9de802485ae729eb0c7eb989d3ecccb306e425bc3882b85590737da37ca20614bec5f669a33cc1276f9b8ee5cee5f14c0207893240e2137e441378200d6e3a0dc36a82b40e4e350ec9c56671c3d6bfa58eda974603512a0b75051b0c2f57175527613a9e8c0359d9daaa1b7c0b19490c4aa01b5a6f8c6337128f459629dfcd75c1eead2f99a33cb61e85a005e43aa6c00f507230b6441d6c2f767db5875bb5c793f9aa1971e6577ac6d06fe606a70601ee379c0c0ea26fac9c202b5f5d56a90c2aabf1bd4b1b429bf9e6a13a67116493b45ed850437c31c08687240e4065f105b8bb193d3093498c1d62d90ced9fa520775e15e37f5f3d955ba6c5fe3daf6415acb1f7080112b391b73216b69001e0bd3cae32b6f4aa6be7116a1177d79f10c1ffcbd4afe2655e29dcbe5de13044f85f28ac2d7384040ff808b8bbde05b15e6eca734fb3127093bf50612e3564f709287788f2557758d09691e21fe5bd1043a7f8c7072aaffbc74f107d2a4952fac14ac5abd2a4b20219eeae6d26fc58f63d83bbdb67340f336060339f711d59fddb810bcdd9f025686ddcccb037dcaa814ab5d86fa12487511af9ead81b5f84ce1300180503774750da2d8e4b3e8562f28e4483b17cae025207a7f737f833a6f58e4c8e0ceb27b66c5db252c04f23b59417e5b7e18db8997075cf64ce7f5c51432cde664419cd61b5840e829783e2ba5a6a34a918a7a0f77080bfe22e0622dae0bcf7ba7bcd83bcc3106a48038cd6788c68461a97f0a56ba764bcc316af112e29c364bee2a70be2ac25e6d3ac326ce143700e9b2499a1cd4a51505d219630e567c1337b21673eb062ff626d018f259c21f2b5a69948278b159175632d1f32f79d845dc4abb7886e1a08ee3b88040072105a9926181e818ca2daf203ff70a6562888fa3f5a9e6ef4c38eeb65dd3a6ca8fbcb9fb549ed56df2335029a0614b9100052b509b230ad9a1cb8fb5932e4ae32ca8fc9c23c8067ecb0be654ecddbc4e382d998db6794000276abbf098d6a790a11dc838ecf4d000715c357ffe50aa38e4666f2e0a8ab14aca5ad9482220a88cd2cbc4d3db53f4388617aaa4309e569dfb303a9f67223ef35a3a06c58fddd0b41a23fa72bc75c92e87081fb8d20967be2418c02ac28de8fb340058582835b2b06349d2175dcba9e8e98d72112dfc8bd8190c2635ddcbd5e5f45d2d0867ba4d578f560008720399e36f0c0016db4b11c1f8ded6d469eefe43f7cb5ebb0423e77c1f7c7d4c9c8f0f6faca36136481fa58de5f16c1d85ec49f3afbd631064294977b6ea46bd406a8a8513776c4bf8e8dc5fa51a956fb4fae503cd196334fad5e7577dd2d8d80cf42b28a8a9b6c231e0853bffa0c8d7632a42e6f5e0d572a28cd112a61809ec7184bfbac717ea5b701a5c82398882526f697f14e9cd92e74680c96538cf10c830af5c9c025fc37957ee3bee8dd302378725451ead038bc19d15b6dcc13541acd269e56aa7e7fbbcc0dbb374a64591a049196cdac06c5072528cd936faee2543aed76e6976c988b123d3d0af907b0935ba63643aac5c609405494026470576c7ac97814c6319427f0980aed844e1ab575dda6cc9ce85b09ff34a0c7a0462e44f1d12975fa53a8f808b09176eec814e47decb90bd98eb7195897c92024e032f12b2619421f09d0cc3b049c959c260df7d0beff951ee4a7d0c09191775b9f93a56e489153646128d0488f242a1e2d2d13eddd57c9f1014c6fccb2d672ecfab197e314b5d9c538605cdf0450106c84026eb8355c51ee643ed6e139c7dec81c50363132b5a43b0e03292f604213c9a74dd5cf7812c6b1c3c7345d1c09fe022637c523651f45738aca92e21a5469a6136083db2093eb3d65b71d3c09c9edd66b3ac95507cb7b01b3bfb476cb7991f5519969f6e9675024464c4765d0d284d2b4a2bde2b92ae7c2b568f3b87f0d7687bb1eabcda4d6baff5d1bd1159911fbc7c1c8d7ccc82a244e24bebfcf622afab7a5cd71885ef59519d1026bbeab7bff29bbbe050936551d85a072d65e48bf63b4b59e80eac3e6cf47f1beb9ecc9d2729ffb9275e9fe493f33579b7362750a3b2f9e1a593ed3461dacb9043a7b54e7a6d695f56d9a72b233a8e88b78478bfeae62384cd2912a7fde98bb540b6b8667f54afc06d05b2f5c7377c2386d2fcaff74596173fb145269c229bc0b89e257ab08c7d2fa67ce08d66f582f74650732ed310888c0cdd439e4071daa714827ce15dbcb604c0f90e71e499549ee8aee15c1a6883c97a7a44150832f8630458db9654581be7cc316be3e439ec88a2e6ea934a3be3a858d19143565159314905920d88e35adedcd38574b87c115ef9c1887eaa9399a13a59011c2886013089823510ed006a4a0095560835ad29abc02f4155509b995acd9171d75667d1f82cc339e693d73aa6f36d9462f86ce2241a62c1c8209562aecfb69e29e660881fdc7f182c064da0e39426dd50c23e2f49d2f3392f0a5c0b81b966a6b150c79819bb3f8453d4d681e8e299c05a29173f0c4e836fc8c2477ff32178ae62c20f0dc3c4cb209e32636fef02463282071fcfdd1fa70d7e6748465fab71550dbebba3e1046475d687551c061db1c2c42cccda9beb7266272bf8a2857c6be3491f8239ebd3fdf49cb24abbf2e5132f52d63f533482a3786d8406ddbb1126288c31ba849d1fcdb6bac5e76a5e08556c034b6fb7701cd311c70be1106643d16811f45cd0427e12f73d6495ee02a0492cab80a387d9f1331f65a3d3252b9fa081bb16f79c56589e1b1dbbf0b97f8cfc84459dd9c3e5310a15e3f213cfee6bd404a1d201599fe45b05592378f1e5e2312788f13a4f282eec1cc0c514c8494028886ef223cc53ef158e0028f0b1428b9cb3b8b7b7034b78eedf8db48b33b8d9bcbda2acedf96a156c8a210390aec5d981f04541dc9b64b3679ebb39d278dcfb81dd29c531b9b756091a41c4d6c8de9d938bde653ae2b58fed99c16137fe798a9789fe25056e692721016ac4d4edc1b2409bbcea2799eb42e3687542252f9d861954526ea86e276df5ffaece88b8c02d6db0ac42b8dd338199b42ec5ae68f8f9024f1529534262e8da8a98de45539437618f83b384110b5ff1e08e2d73eb5f892f36405239b6415072f319a111cc6bf7ebac70e21848cf600555afb8d722dca12fffb9b28b7e805407d7bc8b682d088b273d2b713be2037a74eb8ce6cdc39ad3278dec6d06629b2f728eb09b15d1499b76a4f825db3ff98a85673eba75da82baddfcc1171dc707c87da6b0372044caa61cff99b1d6692ae52bd1f164a35d5157df1d232a64855710ea0c909d15aaade278c27954df94ebd23a55f6f00e6ccad916aafe71e6e1a323fd6cce072248c1b791252e87ef3a2c3b8266dccf0a7bdcd80f84bc21157f16b607326a740f2e90e75f469d8f960e08070c58ef2462e27777b12c9697e205d35449f488084900b982bbaac32ce2dc9a5790a243af71fd7d1cdf286aad156f32c0e34e9bfca28a6420c1c5985cc296459651c12cf6677c73c28e1f045c71d81208c27b0f8ee61a517678845ef5ff5fbe77f8211db21f0ff57e086bc49dde893c0743ad69b1dba439ca958d8632a3d6f12fa6638a41aec4051fe05f319ce2303a37aa9c87f599d6755116d77709f29c8889d00e4c22d169e08d8e4c2cb41f85c006bf7386f4668b27a105f2d7b7b2d75b62dd0696deb899a007cdd27077665bd7a89c00b46494138ca7353c802c74297071304ef009f920559af44f5afb4ea65e8e287587a471f735a7cf83df28fa5885847367a94900f25ea740d1e67cddcd70efa11e3e1fc3994c0647ea1e776db12ace6cce67d8ebd4d3115e582a662307379997cb02d3a237cf65849d1cfd2fe544126c1021f511b19f1033f7bdf8ef48408fb5d0bff22a5b26d9564b0610a65ef6e802667ac0be668acc3a40b79afb95a8127a511015e8a5ed52415b488517486afda890f7f49c0ad3fb32809cf34dc3fc79941381abb0052f03b1bad25ef69632078497ab16ce8a4cb90ae7cf148b9722a30c163f8d1353ca9bff602fad3405b27e6ed016b3c09356c36a769391dd8d64df0df777857f4d201faa95677f1ed5a8fbdef26ae6c9510f0f7302414842f5fc508d26df0c0168e1aaf3e473f93276da48ddab60ad93dc4c1dd3fb5e302cae528556934446700f379f16d6b21a31b5de3c1bc2958259590fcb5aaeb6ba26b3618512a50ff63617d8722b918371dfe78fed85c7c29f86c1590e0378dba80553692b1e8ec6b14bee89883ee4da719ccfb02e0c9c9363860dc5a38d0858fee1f118bead6be7a910cc1fbbee0ffb28011d919d42b03d6d89dd4a6b719ea2bee8adc2761774a15470879fdfb3c15c64c36c86d63233de76ab76f5b09c58e1e5ebef3b856036d8009142e0018ac39b7d85db7b22f3b80b59dd85df3663b3c73741490de5b4acbeb38ae599d4ef6db3e77e27fcf690d25be9114f76b81e2e44a268617ccd6286a4d2462c4765a3940091a882444f0414244ae4b814a910c8da668da567b891fdb9ac55d83b330b24bf507f8f2bba97914d1dd8a4051e9b4222d34bff2947b2b692fe7345b896aa53e03068e3b0c2d7fa127a4eebbd603d2b184c144ba19afc62095e3d1d33e02f91c16bd4c04b1f51b5e269a3522cdc35f69edd3a6d72e8d25a35cd35401420200f4874bda31bccc8f700da681b472191498b526556a83638ee08eda421b7e878fd8e223fd5eea9e33bac5a62cfc67afd316ece7c1144972233609e39e1b49aa3efaf79d5e2e6e7db78e3e516f02397ef2131abb6c389a7dc2ae692426868da65239696d2605d51ae554603c56704d7459f52aaf55d6be46ff01c217b896cb7c46677e199a912a350fb8ad0675f022984fa57448ee4e2b0d0e4b612e65d54736b8a58ba4c94a52c312d63137496b23b47ac4a654ab314e4e54fd27859cac94ba5e5cafdea1b26008822985b7011c3766a037f10fe6a001e053d69665c1116e09b04208a7885cdc2162885e7ab5fd4a2f49c1dceb6b3f33298a61e74386deb6a6a8f2539625168019037718ab3aa2216abc181886301fff10e4559666fb47fd9698a94fb04fac21fd718b5623fd870869b2b57b1493ab21f48f5b2612f5c180854e273a119ef36d307561d55f0efe5e2d7c8a0d44ad558845a5229c6c47e19f67e9e4665466b0434e87dc7f8a657f98c2f1740f94605b78eb416da9038f14b0051e722710ce7bd910c7052dbc412199af79196f0947e70fc970f40cbd1389c7c4ddcb5194c001a186687bfcefd88d72a9c08d01f23dc24d46ab19b30a16b3891d6347804efe8bde52b63018fcc29279ed6db69486399460074988502539f5f191688141da9dabdb0292fc0a619d0964ec0e8472cbfe4360a39bb91cc1ae75535683a1d7383288d36a9b32e4c26a18844b47c260fb9516ede85faf39bccd938a0f7fa00501c36245d07a87d89cd7cf3719c861024eb4284a522504a24da7b20d073508e0ba22094a407f1b7f7b4f58e47cb96af09a5dbd3beb46928a27adb9936fdb868d3c882938e38cbbe3f6d42a63661b5896044912c2ca482ac0b654e6bd5341db5908f9a4e0cacd6a659e392fc02136b6ad312ca0f69be9d66cb939f409b876dba9bde4d4f87938bf9b5deacb1a62f8438293bdb2281abf83885d778ea8046687bf649d024331eabd270baccb0774e50030476a5f8f8f7a7972eb4981b8ac998b22e9cfae0a8bfe9ef948a03fe9d5660d6e737b08330db4baeb6e9d06391171f73552560f665b6e90d095040938e3565dc6e72013e2c9bdd8433b7efbad575468d5e3c436d70d24635094c4b7c52c0e90668db8a7d5f3189a9ecd427c26ef0b8e84cebe5a44c094c048ee002f0724df1cd792a81fad07831ed916700a6e86604ca5c5101b8904e902cdc618411e2b1ed3a3ebf281843a504494092169685c13d717f2e29a8ca28e3ca83b4e63b90c90c46864a2841bb66a3b34a1fa7fbc54e2adc4843cbed95184271544ad1c9fe634730e6331f6627d6f93003998dde2c91dd3412af0695a7c59780eab8377b4aacdd06cb1a22634f88fdf604a828b06ed27248304a8d34a592636b60a309d6a0ebbbd1a10a894e2fb3628fb3da68cd5add5e6fc4a86fb9fd54db1edccf2f3e8e8f88e5e4b84e68cf3a1b738f2a846a0bcaba93a2cf2ebfb8e90f0c7e171be84cb5b949bad6b19e036bac8febb387fc8f773d7533cc0701fac2fa77e0d645ff2cfb493edc87e8fbf2503a961c1fda97b9d403c5a61ebd13ef218b0af0a0f36482cc4f506fbcc47daf07a6c525bf1f3dc08aa77d3c90ad5fc3d1f1620280648c19a33b191c8259fa1ae18a4bc8f4d44d0f203571820a1662680b5ff14151d33633bf1743c2c33463e8a0408762a149f55f06d0c4d2f69e2c25b6f689639a94ba484dab52fd6e1931835e517a0f475efdc235f50609a6cd99e2817b2d9e1afd4bfde743b53e54a75ba9c9c6c17960b317e1f48f31b07fb1dd878e8407f57c187c02d36e4a68644f601a1837c07ec6453f9a6b99db00b714ee93680e86ad204fc506b8903aba06f8681d4970999a250dc5c85f1dbdd2c4316ed115019a9e6cde7da92bf0c24da9159873379fba2a926fb4332dfdd6ff418cdf024ec9eeeab2668ad1a3c1d3e2b1de09b6ed19d610f6723abf25d1c8f0a852f2ba977d66ad80bcc90bfea283202a8baf8e7e80761196dec82277ad5c86a4f0b8e0cc41c947983fbadeaaa711192e7e161b61b594baa5f39701575237d681b79f6578cdbc74d526b2a0534071d78aa9cf49f6d818403e4d82a3cd1e4077dcd281ec2b0980dd46af6f461a18cba7aa4392618c56b415ddb936ab4cd961e999b3b0a5843914d6538f0a15fb0aec51ac2932c6d67b824f2172f1ece4166c82b91799f8eb5c40bfc3d4481d263e75bbc93dd6f22d8922633e96aa3dcc16e730332bf96f28512ec07367cbc8634b846ce63c825879ee29ea440fd6a31daa6b751e71a7b9b11aa264871387fb8892f06914b0a0ad5d72cd8d81f2372d873548e587a4df0747459ea1e2e38b2e8d6de5bde3572aa30da6601b1b39d9ea887e2ccf88382430831aa4fa1e04667fdb580308235259d827520807d3bcc5a3ef169d46a2b8eed530aeb63ba26590bcab0325bb8cce3518f90914418c2146587270d13a2ad53852a90ff6072f7a3089ad9254dba052bb00f0761e063ed9b21afa1258710ff433986a577b0e4b63875d8f3414dacd41438a8b196b35a790d0713fc90ed194852ec02ae10dd6a1019d61ff630a9bccd061c49f68057ab80b0bb1d7e9baab67778d4fa88b3d451937f73061b4951840d3e799182e6a1cfc80332f1400457234390e330bc35b72db5f9d8a33cf2a6c258d07b0d35923cfbd8a098a5abf8c5647553d071bc42b48329ba099b943272b135053d63951399062c1a7688f632417f312e84499c8c3c1aaa45c1154c69ae145e6fd21329fc8146184ef290367b1d90fd49d176d1b714a4d1974175801e2059ca0060488493d96726b6636525d3e20d08aeeb2a187a81bbb9b59429ffa928c4a0c2979b3a4aa2acd2d1cef448c884c8a465eec91a5293bf6a6a8772cf1aee24750a28651f881615882a36801cae01b57275551a37ddc9d86413b67b9040bc4cc871ec4faee1930b42c3c62c03cddb3eb04ff444a22110c00258251740890a001332d22e22dadffe1fb40c19bf99eee7fe203bd6bd4c8dd002d2a753b29e6832467dd7c7a6715f1922a1dbb0c3b02914b7ee16d208449fc55b04ee6931bd9fa6ca82c2889c28d0549641693eaef3c90081ab74efa6900b90a58950fd2ed454a911b8ac5c8b4e0f562b882f312a7be03eb963840baff3fca946c51b34c72f9bca90ff910c839852391bc45610870ff926e07e6ef5f9031b0ac9c28af888dfd466b486b1d404320380bd8dbed291c06d36bfe8a312e13596ac43030358b554c5a01231661cffb8cfe4b2111228010a9288ca4117966192e0719126eabb97ce01bfe46b852df3f65c26b73bb3b28251a4bb3964dc36aae8ef1e63b444a701dd6c4e31b3c55244de87e098648142683385ca3870a9908f9faa83ba8140a4212ba004b4f9a73060a25e98715231c0846c4b6fb4a9bc26d05daf58bfcec5ee4f970c2befd6dd9ea146f73b9578fe952ad1482892bc69f0c4bb88d2a6bb0fa14d4cbf2170ffa656186f4856a619fe9cb0ef3ee200e0508460df30a2328c85b30192feafbab1007b58ce8ed512c7608576182203f418a3d82245e2f229730a9fc436fd3aa19b6104ea96fb964751e5089d71381cd183a4370faded01970bf11d3cf9af4bfb3a34c3cecb315bb03ae806ac0992e65ed45e1f745c129d7428520c0b8832389a1195824f170a199c5fcb7ae6ea0f3f20c1e666d7d0dcbee659f790400fd3060aebb652e9debc51c8341b46280c7fe8016b8b3612aca83323ae61c60e6a490c40c9e60edb8823d48acc8a706b75fe8fb9c2946245bfb7f9b60351bcb8151ea0bedaca95de18e36bd740851d5121b4ea49b70dca3d4315906f6f2d5149b1b2ea45f0659852bd57fb3cec108646d8bccbe01ef077da0576a8a16914b680119c8c5348d25dae6a867eb3d94f89c0578908cade537351c0b5200268a888d00d62f74572d92105038b1a169e128b20e45ca24d7ad1d54344e0efd25120e1021b97b4df20c2bf90fe90f5e12688045fcb33425ef76dd865e3bb087161aaa82434bbc8c8655dbbb0d81acbb41a1efd6f5e11221046b4c9729e9fed43dac66d640e8a915290d5946d9f60a1dfa42f61233e5d75321c471958a032ed840a5d24763fb248c7cf7b692bb1f46e75c840bc88a6d0f50b0382468104009121ce7930f321686b124f24e043a7f1a10e13eee1b69dcb12da8be02786fe01adb0657890f5d44b07c43a2951926b45ba41181cba1b18b1e24001b7d03f40fd94cac9d6ebc2e23553692775a1002f55835ad6b9fc1d12c0884382f6b17cea47c732b8ce48d2461b16196dc7d12af00ea50af2293fa154e59933ab531ed7cd8ed52dcbf2658ea9041ffd1e0c3661cefb954bd405994a440b3a62c1d0e1438cdf0604dd45a8e5f4e28162bdf174a0b7d0b876dcd280c5c923222a9dc084bf281e7fc8972fbfc171c2ee5f03b5b9154bd130fb4f3c902ac21a0cf2f17e02aa6be69a11766615fa7b23ada6fc2e013ff8bc7134ba8f8ff078d2f83b5521bb24b78b484f921957fc1f1685b6233246e1eac421c893ac1b8c5d8ee88d8db691e9ba88d340dffe6338211eaeb7e9fdb10021f578b1e4e1faf1943e2da422a6aa2cbfcd86e7f738a6cf5cffdfc9d31940e982c02f8314bb691c9aa1049b286b813fa6c2ea7be9174b850ba7822855ac157ad755da38a63b64d61f80ddeea7d79ff18f1f039be81d8e0a1a1c5d3cbb1a2253926079a6cc6c6dfbca8c7bea601243802079bd29c75b9a50cecc85a31cea1200cf933abb95e26a59dbdb2645a4fbce1d11b73412e477c2390769cc6baac9ea232b4542a85f91d79bfd27943a7d9b747c526aeb846ae84d31acda5da4bf414ad612727e7c1c959475cd095136d8eaf0dd07766404a9d8a1211e1f5782de066bf09de42ac59c4f866297506face4df046694cf94814d3971abba3af18ef5582b8243f783fb477e99960789f5729e207be748bb1f6ec964b8feee88e2278bff93c7d162c9fe794d8f34c81d568f1000bf3424cb033336b9ea7917aeccc41b40577d385e9cfc17144053c9e648c22172964d3c471381c3cb2a877c3fcd562f29ef1de1accd196dac5772f92d400b94c27d8e4f1f6e5a92ca6c00128aaea92319deed46b58aba56579af5d3c7781f172c805103577af831042276b78c803cdf799b309ea184fe5d44371663cfcf5b5b4fb495203a3c658da743c48f3dc78e962a9699591027a0062515d46fe5d3741064ce0538af91a747fd1d0c49fbd7ccf81c552f46fcd75b99941675377cfe0865b3b63cefbf8313514c0b2f9a037fc542cb3a46d6cd29d4ed114796751ba168f0f34ccf8a8a4ecbe0ed52d69b649a7e875f00e32967f6dcdb44d822abf6c142e42580d27bbd25baa61a341131dd0a6a68a224bf055a4637d3bd6bf7f7fbe5b509081812522545d3d7f049d7ea93b9ca22527248f5094e4538e9c042980d89ec4342834e846f3c7c7d01b47913b1330cfff2bf9d07be0f43cbbd1453f4e42316954cea6e4f19683cdaefac2b75e1fa07a6c3172fe4009206bdb586759951d7fcb2a17a9b245af9330666b52ae46b63f82d48dadb62533744e7db1b67e0149ba30b62cd1446bcadaae12f94d99210f2700d173d230354a6c68213818b08de796044d8ca901ec043f570e546786870a366c923bee48e284b78b8a82246e1b2ae61d666baf90f96d8b6f6b6995b43329ac904bc701c207e82bef26f4bed9865966bfefc4731cce3f4fa40f687f0709b1e9fd99e7e06b1d0763b4d2ae99284e90b96cac6b44dbbdb9d6a059afc29010590fb29d071c20f4e1e41b7f178091b71f3f9098e653a6e294686b5c616fd6090a839b86d9ea88f911c12953f525719d4a41f177e893f9a7ac8acc5fe2bd09e57acf7122584b2b55cc30490b51d89ad6dc7de20158809e89aac40d6b673965784c0ead222d564e8f5b33cb51843bd737cbcbf53a90546dc84868e11ab170800f2a4da4f82904b75c862909a6a23b421146ef8d1714f59b4db966da56e4088872d88bd290c96de677dba023c80d553a1539aa10d80d3e5a7a50f5f6be924adb5746112803c4460a1be945472bc1b477e654de7760e6a565e0f27fdea477efb88efb9a77e00158db10ff1d1fa8b7c2d336d7f641cd1153a3af01ad90d6d51c7dd58eaeb75b369de120f79976221c495b0fd2175e7067c1dabff0c1082aba9569e5814812cd96694d076f017c345ba8a583955c7457d2ea812a5a9495d5d20edcac64b42daad5c18b543457d0c283b4f940a08c6aa1d60e2249d169252d3cf84445555c772f22dee0368a2d7ca841796af1907ab5941c6dbb2f8d0eb73013e9d5917ea07431f34af9d626e645b067519dd00f935eb09beea30973542024834b8b18e0eaf6444b171b753444f08fc5a272ec437969c0a489952fcdb2a09f37bb5ab4552eec9639d15bf6b9709df45a42bdb0beca85bfc01b6238f7cb450f601fa859a0dc689fc92617d0c314648463276b1e1df6040760e9f85302de4abc0e93279da28548ad036bbd806a4e1a4b5aab48db7e6a08412dfc76c97098cfbd630e029fd7f1be60b858e9f37fbe506bad4e29806a58b4b3d21fe5091b33a7bcb86b632ff3a09f7a2733452781434e4ac6211eb3d86ad151d3260815efba86ac87937cfed81d30087b7720fea14574284ee6b7d59c2c9578051c5747c1df0a928b5eabcd802b610eb936882bdf113a6cf6023d9e1e311e39bbd12ef00a0a8dcd0cb54d1ce43b0d7eafb7f2cb872db981145b02bb1a41bcc7817dfe4f8a945f5e55605f421fa879ae340e769dc1559d500be2fb690761c6f05a7fec8888556fd5ed02beb4438c2fbfb56786b8ed50569b414f2dd4746344dc8511809494669429151b1616d77cb78b8fc5d99a768a3f1356b40d9198ce7f6ea8c8058ad254948016bde7b0ab9fcf54c26d96e170a150b7fed4168d26550f623ea26802ed1785623832a68a2473fb27a45a91c15907e4605027178a58e032340ddd63be8a63b967735d545cb53b8d2307a536602fc45c39aa5903a9a0a7c1ea67bfe9d82cc52c88c6cca68fa53df8e2bcb85954c70f1dd86ce30b02df353c6931131d7f80a6b3c707cb5b8b061ed08f559c93f5a811f467420868c75270c366ea1d76b0b8e9488253aa5d0f9b9009fdf624a917a9cb2ffe033fa8f287473ec6f2f47ded260dcbe6622a00b0cb0e218ae15f0d1b8eef3aba6c80d97f783337379fef70a8aad1b1df0eff3188589c15e4d7666f41b96a58df1664d9962557ae6b3f9084a1de3aadb9407ed5013778713c372221482ac2eee465a9b47c4afadba01629a603cfa31f39b9ff4896eacf00c39260675e6908f8915a5737d28e55cbe8634b0aca89c94710d17a9af877de4b32f1fff5520b1aaaeb09a2067658654101882405fab32754d2856ed7f455094351eccd5da678a33209e63f09b50ec549f1f08c0e74621a5942394e78e8809b069f8c2fc47cbe5a6b602369c164c2c249337b0a9818daf96776763b2c00a980ae6479946a523deb515266f559dbb6c13f17013bfc80b21736987c1569ef23a09282ba7442bf0d510fe201fc486b771c9f03a66a57a3f0bf8b9b28803fc531b3813b067e80cef25f575cfd22449f58a8eebc50b5e39d599977cafd8ab58e53aec19770ee54c0437ee9ed03646ef46dd158886687d7224636d72db32d85b41a5e5db4ededa21170c4ecbac6b7fe8e61eb8f4cbffeb673bc11a0d5809d0e2cebeba0ec6fa1b74448d9fbf3652bcb9a7b3091747662be3aa9c9b60551f80e18189df2dac0d04abe11b198c8209055226fb705ac9cbdf0a6dc474aabca96d86f4415745222ee79b884b0ef0eaeb4ae613d5521ed690ba62249cb85f6acec030a49c68785cf941de45ba8e9f8242cb24c09007c9e311a6b8ccb90dda25b363a59fb8693aaf7adf2d7e934d4f5f537e658306b7a3bc9f0a74b4e0949a7a91119f36518a2400241da868602f06a18000202c13524cd3f39398442a850cd99002ab8beeffb1699cdd0719fda001a4a303e116f38a7741b91239480c9861a5019e990d300323aef2a029e8fa8e5c0ce754b34342486aee483b89f9117a7b05b755c332ef0f2292d99970d367df1ac5c4aa82ef7b0fb4b08483aa7c1b11a9b19d75213f7b7162d589aff9ddcf5e3ca01b3c1589e7cc7f086a3b8676b01511e998605f672073c2f37db956c5daf701ecfb6d8a1a0e2629b509c426bbe8509a52d510a32fb8d5870b31d92b8909ce9e33feaedcf48328435e16f1a88ffda92165310badd6931208ddbd000c60ffb0af303a96d6d9d4d7d000359e4777100b1f19f0062f5626d0a57723698b01caacafcf42401b4123ba383fb1cca6c4c602394259ffbb05d7e6d2efd93012aa37dc56374713786fa56b0b5ba582559255f0936ec2277d612492f358a43bab6eced8ee3ebbd85e09e5b47ff87217ae843bfc2639654238abdb5981235baca9e81432ed4a538a135e9b1c412a0766d4052fa2dfb5d5f46720cc4ce066240ae5b230ba78875a95d09c028da1e6829cc99941a6ee74591570fceab1e734856d2488ef8c28b5fd088356d65e281cb6b55957a1a3e65993e2f62a79ec7cdc66fc9614c7b5202d36be0d464e592b2056e9ff41b380b1b0633d57c8ed464d4a003d8cdc8c18c46d3a3e7fe4369c712a7839ebbc0ef759e71dbe3be456cb0d7be33add08a56e2a5d4fc7f56d35193193b3b8999bc468c77bd449d14120c40ef6064d7ee7de73305b99216ec9628c80d8d62c80b89348f50adc7462b036b40404163d7bd04592b24c780e471851b87a09a33ae381c828ce4eca60b06f62f75293fc011772bc6bedee7b6469a3a1614dcf48e578cb5b79981f1a1c8b13664c29858b5a53844a3629cb691f517d28bd7c624c36ff78e8b4327019113f1cfafc8a6f7ea9734f269873b8428ae843c09265553e22ea495a6bc7d2e57bd50db41018753cb5332b8831ca5cae08b3d1b84b834483a5959b1b11149ffe32d7557455dc1232206bdd3c091a8e3aac9dfed8c8114ed15541368c95da3888ede0981cdbbf8ac423e9dc3af6738db74bdb79370b8965392ed8161c719cc44db4ccb073e5ee4a3f3008b671f608298d0e1fb6cebbc934e2fac7bef21934a931a00d3244d8710b6e116ab56f91bad0b82f8bf64ad19b548a38f2b0150ac026ba223f0078a504d9550f301702b5dfbbe893e4752d702910d479dd9da820e718bd2edf4340049680f8f5c4609d6a9110fb878b918400158b390d2400b216ab03b095af4d8f767795332be4da14273a49420e3ab3e65b7f4cc6e0a66ecdffa2af7ea7e459fdc0501d74b73ca6bad19eb60a1c629ddd50f9471e2f6286419e94ba7ac0c8195de30e67f4996683a67b15607e3544e1ffcfd4a28e101433510b47bac0025669d3cb149fae2025f16fa487be1405ebc68b268e44109dcb5306733bc8fbdb36d6decb049aa1c370bedfa2a3143cefb5ca4e1f58720cc00d58b42cda2c0a623fe2936b18ffae79e5bf52a13382f81d3de2fb53a5f45cff5057d6414b509b142643984ac32323cdb5c6e0e167e06955830e17b809c7a82dae9157e38a1a5b1935b30d22c38a52a74b8c95717c17339af4fb8fa4cb884472375ee10ba9d7b4285eabfb063100cfdebb88ea49d05443e1dfaa2f2be6e9aaf404a3719ffc8fbaab90fb6bc67f2e5b3866c36d3591012904029bd3b7e20139e4e51d56a0cc480a93e5a0feef701d43941a60d5d1d1c1fa0d9a167de2bbf7a4006eb69447d51bdfb8554f997bbefca0dc5b610c572db8cf6d15abbb7bafe577de45cc762db760fdd33a47d0fbaee4bb24f4ce94bdf56df9d09680d8f6a12f9c91788070ed8f933916ddf7c78f8222abc4f18d7d8cb27c1222b5bab5e6ceb8c70f66e44c2e76aabe4241cb2612bded706d3ac4fac7b50649227d3c3fe96a4a63e9baab349707ccd0aa4389f599bae6784ff25927d9d41927be22ec2dcc092afdd36d4228f2d6da2227d2bf0a5e918d1e9cc9d8f0e4f7d6c9fc795ba63964d15bef708902651c82fb30a9c7ac98c069872544f380050c2c079d59b753bb66a586d1d03caa9804be3c5edb3bb99062bf208007539368016f34c2692b687ab9b6e732e7ee74275bfaf3f01b12d980e9ffdfcbd9e774b92dcd11a55084f8816646324996aa9271f9c88b780ceb55f64258293a6aabe339189bcbe4273433b99518e4dfbdabb6f96c5cfbd69e3bd03b742028e894c56a49fdb36d6f4c7e155ffa21f4bda1d32dcc1dac85a78351ecb0f4167137ab10ce18fe3d1814fa33fe07f3f175f86357d1bae2b57882b5e372d76c1f162e4016e1c92e1d37801a27e94f85b82ee9cf10d1f99cdf2ff94f58f765d1d537ed0a85273942362785c3281ef7a6bf0cdf5f26e5e28da123fcdeb55013704bd34996596e6ff99aa226552d53972d73ca00396dab7fc8cbab52d1f0ae6bf2d3d8419f96d1d3641a5728ed18aa29b2022478188a19c54d9dcc5eb0db4104434ff7427eb72088b82d4d59c16981e5623545951fe0b6f701a2e3e4f6a7a1f73054ea651efe529985fbf19406e2f8ce23c6dc7ec0adc7b75850392b66dce061866942ad575c276f23971c63f9a798f2b671cf0dd2f3beada409866563c827dfee7a8933041a0d1df2fb9e080553fe7902b78d6db5c7842b5787f05bdd8d84532a98a58a3ead10b92b54a6d129d7516be17def96c5b636150845fe0bfe31a06b3580f9e0a5a9d0db4f012e5aa71940bb6b7f25b3786ac05e109cb11efc913d033688f5acdc957721d701c3430651c97f873f56aed2efc9fa467d88b800ec7efd96d6ea03026f5c26be6f4a6d354b0d94e0b20dd62d747e50188994c1499d5c793a687900ae18152e574c57ab14b0b7dcc5ff5905311866838dfc51b06a27fa44d0785c74f76937e55a83824dc773370fc1a6a35cb0e5a54398dd79894eaffd24e1e238b43a9f463ed45a29cc7f5a604053e3eff3fabed153a69bb02247238bd4b29b3047d17daeda575c412be79ad227e31bd6f6df4ffa956ab214cb95a46dc96348284742a2490bb8b8940c23e05e795a2709d514345fd898b3a0b5383157008696ef5fe0ce34c13b995492f8c79554a23fe036c8ea82b93cccbf175a5e3782ed92f43cc5c843d2c143b0c56d74fd95304179afd7c0dfa8dcb2a185a7a6e5b2b8e442e45f3f66fc17946983a9e9a99fdea1e8fa946c82ac7556c04415c1712c70b03ded7d7b6123f6e0510bf5835c4407cc9675b5b4717675a552820ee58bad4045926cf35ebbf96f89ff48a38697d15ec7b24c1a1297a16fd1a911940c82745db01889c39548c184d92e33cfcc3617731e2531401ee7590a4a4a92a931d99cd93491fda89380f02ec8e1ba46d0c04365f372dda7b04a70937074862688f628e05cb387ad52a3980b00b612ff76667f0c20467631c0a2acd9d7c02e8a3a1b28edf9d0344e1f12af08602eb713a0476a6a7a50fde937000188eeaa04253f44ea8e956f3286ca4743b2d9d83dd558ae86e6ca8684949117b37fd6d3b8a8909fdefd0397760c94f34a099110680f631ffbd46e48124984464e4aab83067ce478b4479ee9130bacc787d0f1eb97340cefa95db97b925989fccbfb86e6430749af7e1b56d0e6b058324a4128c1243f5e6ad038fa91e903e76dc0161bb565939895063e1d258fe36f9230300dec879898d81fc98a7d99655a4bfdb6ff9d1d320b463a4a9c01117c96114a1d53964543e7afee0cac406e4aa069e3c6c7125196f3bf9f17a209b23cb480cecd5434fe08627c4357aad90f3194eab7aff140e18c3f0329781449bcf42448f3ee96c8390c638a0c2334ad749431cb121ae0b2618e39a92c6db842eb0b5b1f60dc7cb34b67e7164a83b199b02580e1fafb3e26fb44b15f4e36d44b6eae9c3256faa84fec364a8a4f83ea0493bf54ce926c423f71b138d0a4dd5cd1d76c079c0d73c97996e8545df6bfc85f177b713798481c681e8b90f4f5e0a9bde574cb62f35441c56e87dee76e4b09e90e24f2bff7a89b8e340ce8af0cbb7ef94bad9df15dab7d104f9a4f62324a420ea89277d6da438dedf3cb2e8a657c29df9318e13d042859f79643b3a265e720349719ec743e69a152061cc6b3b3da45c0f50e1ad0b888875dd23f5413ceb1660891eda8c4294a8a43580a7a0406248df2183249b19d8a1d179bdeeabb1ddf3ce6bda84894be20160209374f69fec29938fe217aa02f6b979d9e93e26f49ed5ae8c69609506f7bdbb21cb9cc6521d2e9c738224d20dc7a3bc4b8ee7bf696f5dbb9c3352aee739639aabd800a56591841dab86b49de22301d3de698b03c2eb1b310f84ca42e9bb6ab3b22c4b6c832e91e9b2ba7844efa8ef4e2be829401610824e33b440d704bb861e23b39345db6e394931311c6b6f5c2680911f0c83a0cb0b6df7674f691edc841f4da30dc6237e43e882e993586c44ded6422b2a9dc52548d52ec919dcf806472f48804a11ac7c511423f20f7ae6d9c65c8694d1e94d1e970bd3933396204622c33bdabc8050b35eca2c1d8e4436e429e4e83eac9aafb47a5b0f3654393023a643e4a4d61a2de5e945772601240bcc1a7f30076c120ced526559aff5d977274be355836c4c15819f25502116cbef787a69a8e48d06e99cf41ccaee086e5bcc1d3e55677236bf35c7540df936d6af2f831280ceaa735128f10b25bbdf6435fd6dba974042165a801a4a5f4db7254e0327db6afae2db40c02ab0ea6d068005ec66b24bba300e87adad2969831316af6eeb4a7cd8bfb602a72c334f1fe1b6c914479d22bb0e41f4694bc74b8c4668eb356a348b6bea2ab2449b17318777cffc491d084e44486e92c6331827422dde812a639441d2b68605096184ffb664e49a3bb05bdced10a816898807f5eacdae9513f41233b607966ada606f86df50b3031504e7f4cea8f2a6e6862ebdb92c9d643d89ac1f15317bd604c3e29fc462a1ba7ddf2ed1f4992694ac97f93c0bb6b2d612d6d7a217a1ac206cc64d855889e2e1309f472ec13435ae5f315beb6bb46259bf07b5ff90b09d8b437f800b7a46a7ffeddc11da0647a5b03157404a989dd7a44e80f96a5719c9787065265fbd58744ab273769fb22553700f0c00a5562b7cf3437631326a0e2906651926365742d8d00009845e1a294374ac42f43ffe7e399e5a63b7de715b22ce75f9158402c5d1c997f9df945b7c553e847b0467b46707a8302f19f87701031563159f50f28573cb8bc162aced081219757b3a955230c26f065a04e8bfb10aeae420c7e02f402c4205a4dd65c14deb4c340007f45abe6d81d8eeaca8eb2e33179f58f7ad880d31d1e10d568c2139f84c95436c72fbe0f54ba83a76d3b47d70f489058ff13f77df26ddf1e8ccc0d41591b5c20ea52a4e83fcd77014b94879aba9ece901873ba630dfa3ec60d9a3ba7927c20bd0b22de23456b93b4e2fbb1792ca303e6d37b02175838d89008c57fd3b1fda5862778491b0e73718f9084606ea8ecbf17a70eda435f7ec877c04d232c08d65d917386f6d6e2750ecc2ed597c6a8bf3685f10c43f0ab2a02b59febac7e7b0c7a80913e6abed3917f4764a432c5095569551268575788b22d98f87e26c0ce76a66c46d420483ff0e0e299a55eeff0c33ca41c70d2b27bd47c8de75dac1712ddb5c58559eff05874f90d8846c6ca7ffcbe95185c233282d3ab10a2fe83bda4906f4e4bde3d33471ffe0e1284ff586956c7690fa85c1435d73b0901772d413a502cac518437e5fffaeb5d2980a32d5a9654a8775a3772a3b7120f7bbbcf8727ce6bf1008db85571e4f9d4afe0b6d608de548c6d9801fa2d86e540d2236c052f9885f4218fe9988e672a04443fd8b9caeffcb50aaf9e86eefa66a30c8da2ec69e4ff58ddc6e64f8ad8b743a164fbf3d934b1856b0848a5dea6de4882ae3b5e47c5b7f9f8953d89d94359b7f5a34a50e74bb75e91b688ddecdd746ef6fe445426145fb1300c8451a4ed5da56df74970264c0967786cda6c81f9f387f4e02a97bb8199f16850786154ae57ea38b4df9d530bf9a85749341f831f2dc5853315a65f5683c31c6a40a67109c00d39878ae1bb9784009956d231217b2b9368a0499b9d153281739c7a80dee4172cdc9d2921b275ebcef1c8d2ffadac0a67234ae44d0f972a165413973b507435655eaf434716bd18ff321d0adb18d5bc03050fd6031ba318af09517e1d38d3e1030315f2085609880cb7dbc1da5e78353a1819ffbc9c79b3e73792bd84ed4e7fd344faeb1491377cd67895a940cf80b3ed33865f4c87bcae9d6d3fe8fed7d391006fc08141478ac988360b81716e7e49a4641c90264463d9a5d42163539bd69bd3065f138eab08d7ebcaa43521fca540423e19777f4d09e162f85948ec4d1422d0e1c3db79d650da8b3b18d8825a407282f67b0886a26948fee500d6512ac1cf16ac0cd0d96999b050f83873117f296a30bf83768a56a0e911645532627729a44a9ad3b8bad531b7cf321276234897c265081fd3f967815c200043434483bfa4fe124b986361ba2fb004b1cd4ed70c383443762923fcfbc0d00dc1c9aef1aa5ffd4358d58054caa5a916e36616425ee677745b24ebbc82fba500513eeeada1516c793941c6c8f7654b20fcceedb089c1b4f0abaf12caa2e045102c3e9487c52b1aac59f8a19ee576705d4b092a9ebfd7188914d71ca0e34d837467fee159e03e685bb305646da44087f147066830c7bd07ba3c170cc9453617f1f9fb07da5b65948322a733d897698624d5d2420dfd0e064dab1fe31414021cf968b5b06e41c7d80d7977b38364abffc68ef858a93f43b716a276ea40c914ccb22ae5f859b5ade943aa16a833acd47f4d95d8bace19d8854a06f8b108a40130ba04566b3b3125f7d95bfa0f3229a9afdf4cab3e8e051c945239bce60008ae7eb4cd6cd4798c1211aad38cccaa0bc7a7e098ad952b6f754e3efa56217729f915ccf7084ac22805d1b272629aa3751fe7624a801929c676a71190a9b0647182a88a40ebfad781dabc035a89d86117e9543ed478cce67426b7013bd9ed5f8949ce1bd27e0f35446a4c4e1a041aa9b3eb454a091964ca52d9b42180247e7b6a072cef508881f37a57ad482e68f3e1ffc0660adad216eff2d97ce26f47835661bcdf94f729b4a74e995d6bea74869ba7d13776a90568294f5446c0ae0785e174355bd0e926359e15c1a35a3f96845315170332e31c97c26a092d49e975ca02f0be2baabfb67979f7304722003ebd9761f804ffc4c64ed900051160174d91c280f6764aa573e19cb1872786ed871b1db3b72e319b6d789635b2abb017451aeed391f5f0bd87af0610fd5d957611f8c375e2890bfd60b9b37a911d9166fab96a5d3d017315fee08a809360d1d026d40057be88a165d8bc5f40ddd912400443f1f37653176bbcafc24aa47873cc0ad55312160f677c051cc5da4e549df151569db359e813289cc8cde7365a81a69639d8eb4f40b3c3cd4cbc31d0ccc9f8297e92513e5cdf79bad09fd1ece6c1e5d72b167e216c0073f8d060e19faefb8d3cdbaa1a3ef7c30f9647646620b7a592d8e771eefbca2e9baea48702d83d1be0b31afb82d91d89208640880acbb6545b16bd9940e011611c65de1263e4dfd2508359935aeda1b384803253b5430e85eb49d1e63017e5ad9f8689c206427d72c75bdf46026ec507b17a1089ec45e9814218c6c0ab6a130a3cdb7da94e43cd27b3245c73cae5e8adb5f38526dda695770711e44a9f270cb12392bf30519867ffbd95b84b324388f4e73921e15c782080e2dfc2f66e58571ce146eb1b27cd8020f30b47c0173b5cec10eb8f60e76f5e3120b77855c3cc9c00edaaf2b3fc7c10e79a437b8d16a09df434118a26715fd66e3ae5000263f0788dfdf2e30ef190c5392dd4b65f757cd55b848d2ab091d0586afeecaae5266d94873353f70bbaec7b94dedb99794c202282f5e179702a31b2032de44090878b9f27687c8de65458e28f2c506ad73d4a80ae56f38a157b6e4e641a60e9b4614ba5b9fec4f4d722a8b6f405221322e28f1fc81f746067963cdcee6316d614819dc92d103d8704954f4dd41843657caa2e650ef524359cba7ac4153cb8b408056f67c5188caa20165bc9d763bcd100cda0c695bada00376b2bd63a62b6cbebfc35c5bd01fe6b73b6618137c53eaa3ae9c7cdf610408da17628c744b92e0e89563b0e1a0d3a903f55ec03b93e0e3a8f6b44e3169e3cecc77ecfa60bf0ceba610c27e4994b02db437b7b30f20484304109e4a208b4825e003acb9f5232039df566a353fbc4c9b20507a3fb956eca5640912a1443a61197bb4935490f7b42e10754b4846d81fb4723da2438577671c44a53128c59dd015daca4dc48601c700f7bd5407a0fbb8a80240b73003a8a478e2e426af5d90d5bb15a2708631a3588e37c2bba452920d76181dc054d969c46854b9ac4526ade2a6d841ac012a75e68f1d369d6581d9c26a10c9933a2b8972c1dc2c5ef1812f5d772abbe053d179a753f57f04bbb3c959d90a8c451b760afbeb7af669233291fd4fda0d45ee99985ee0c7aac9e4ba3d5ad1d8707385aad019d1b826942464287a930fd5b45fbd0ca644356e87d4223d0636d2ada83b7228d2b424a0a0bba31a246ba452118039d58ab8117591a0e40105da2dda462646b234fb526c3de6c882cf24469b8b505ddda3dbc0e8f589c05703b0b420421702d239cc7052660dae3461ef9d61284faa3e021a69d77e427f609fb8a1dfb1313b0226ddc1664dce104dad78d6c21965ba5ef6260d99bfdba750179e9c6e6fe48dbe9134a2d318b51ce05316d652151254e88b19e97fe2040e98558602f055562b246dda17a8fc6bfd241c0daa007a4e232c24654fbbea54d8a9c693ba86eb989f12eed1566a7aa8b795019728566b5159659c8dd2049f2f38dd819152319554c508b15d85d69ac0ae9e5d551e84919aed41e195d4a78d4c61aacde19f8acb6956ae09e32c3ee206178795082549784158040ec2da298aa8ee88d37e8fd71f825e8be2b0830802867d5bb42fe8613a5c0169249078e967b17065b51803ff08683d59256135522152a422709ab722d65f6cbe553814f86bb80756bc3c3946c9f7abce3ea7d70c804ef531b17ade2f4f21f3d2adae2f1716ff43e940c080e7a4434e93062fbb6eedc752065f417757f79b0d0943da1f4cbc53dd07ac61338a6340e50f9ec2c4ba6db4d285e37e87765ee3ac1dda13d512a46ac473d6907f244ef2400745a200ec1c863e5416c549d4be7dcfdae03315bd6231a8c7e138534607fb45d245f4cbcf2582e74d3c6de6150c7f72203b9be5783e574df67826f3eee9439b3992d1ea1e19e40865a7178a54ee4612db2b765066d3435f2d498bed4172480bd20e1df6d32e1a689909f464a8213d916c53eb6cb73fe7675fc131813d213491206e286f06be8d8bb225028bcbd1224c3422c54f1cf62a0d6df43be502f16cd8174bd61b5dd802c65ec3bbb4c0af8f13d5bc3dbddf89613075094eb337f6a02a347cd369277aef60672510447024675939c78c68b5ec78415beaf2a9246ed29eb642c92d8129a0401363e8523e6195762b3e88dcb2a1b97954658bfb8c186fd98c63b452fe81f5d2119eb40fbd9a4018a3dacb6e6790e3930d9369a5c08023394b48278900221371955ccb3ca06802bb0232fe7241e27e42ef6c5add4ff062f043efb41fd0c51def906c0e4b230d19620677522564157626538a6be4148267142ef97663ce0dac12e10bbed816504f06b8ed0684d967177377ea0cb4909331150d42b92efce5abfefbc7c9420403c58aca25da72706ff24cdd940bb4c78ed21f56d7e512ad461feec04831fda26af999e367c9a692306dfa2c3e87e088c1990ca7baa75b615fbc1251202fe9f108674da82b51328db082e6ba298367a4af744b74b2de8df079e7f50474ea8be5638a639d601a3add439179448135f79e6bb6ca9c4e6879c90cb67bfda58b30f97ab2f6d22d5d731ad03bc28534a9d56820637dd50e60150e1ff10126399930686bedc55d77838fe6ff601f052256fe4f463e832fe0ab07af6ec8dfeda5cb9a982858650977ede035186cd37a8fcd6c3017002c0a2761b37c88b324bc2789409655a0e9563cc77ee91c3a515c8379e8a1bb4da68e4b28edd4a844d6d8886b2aee2356bb1744e8dfcab256d55d10f10551049ebf13ec0ba09c69e6a4a06c174b130963e6b360b9892a4816423a6b5a050ea494dd8dda773af7f0ed510e5e67c82d78d657118a9c23c1433ea0c333e5b743771444c63edc84c02a84a7e449be2eee2534add49cf3f100f50ae2d02c7ae237a326fefb043e4dab5464c81f6f2d933a0280b1edd4b3e38a4ec2a712d18d45f91e43fcc15255eebd714a4e117e8dff15a5136a0437d8135c78f7cb87f06ce18e87784715ee8489d41eec49033c99e214fbd8f1d7f0000efffd508b4479be63649592ca64eea62b382738c45fbae1945f24feacd1213a3fe8ac8b03a1b383cbf93811dd13bbe195644c30c000bd2215d98195a50441cc94f8f44d897d6611da083a7482f69dc1f158ef791a04282f8c24a8aa859bb3fdeab332e565e282a8b11a5c8abb93bcde8f58cf68117d7800ecbf84c16c8e458f06fdf6730a551d49f03d1032a53d12778895b8285b10139e35b68d96c11fb6e6b3f06d13cdb85c98543c9e2885d64862a71e19de692bb7b960c1a2fcc98072bd4ea3dedb22f5fc3a1bbde2f7fb753aaa287b3a0fe909a3d18b1da2180c84b662de646230fd8188356c6fab1ba2fb429c1d454c814228abb35cb0fa9662a5146443f1ff01508b1970876a083be050f0823baa6aadf38ec707ad40b98f2e711a29bf88ca2c32a331a4ac5975762e45e953b863e826312853eaa631a29982206f46b0e9a44d0c5b1952928d464c33a385d527ffde0dff2d3ac3d8a94c5b8ffe37acaac134b5d19c5d5223a91ffb30729ca6364d14bbe68b428de845951b317a33dcad4c586088f84660155502829fd9aed878df1ff9e2d57eb9a79221b9afa5afece26bfbedd5901e57c90a618d8e2544d0ca28ee5cbd22af637185acbe63d71c399e312815bdffad0c79a8e920bcb5895043c7d508d96ea3225ed199ddd57004d92bfd01bf4c3e368ea60c61868b9f22fcb5a37476fd724c8a70d6cd3907f3c2b60c9585f7f2cdbac0d1b49fe30578b1fa7f090945aec926be4cffa170db61a5780795ba41164b1934b71ea9e11339ed29652d42a6e74681a8641b6d4c9e0863543c6005e93c94f8d9bd291284aa0e6e183671ca15191893b22494db0a32193d2135fa6977dcc935fc6cc72dabff358c5476dbaae49439ef3873c116249292bd5140481b139d684a00844be00158a5e1f16c0ce3cd75a78d319c4496f2ba62e4d6388974c744da645ff2f5ab452b3ccf478609016876fee5208adcfb40afbbf015d42ec88f56644c5b5caf687789493edff44f68e85acc048597aaf6f48d6963afbb425f7d4f4a7852b5a8ab76caddf6a36b21e0fa4bc69e6538e7088cc4b87c77a758e60a63f1e9220aa1374bb9a86c1099104312f0301a65b2614994a44e7c7125736c4e6e180dc4f38b580561866e74dc2919604a2405de2141a3bf68817df40d3c8b43daabd131f53807dc8c49fe289069b72186cdc3c9d2842902d06162d7a64b21c3ee7daf0c608c2592c58c60229d1c0868cb2791cfe019df4d509d26f199794528aa712b1d135f229f994cdbb373f807412e2911637682d228236c5e6aa8f92114315b2ca2af9a4d3d5ed47a293dbf2fcea8b00d812e13dd7ab04de8ac78d0da7010d962ed0b5c64803ef5c7b2c262b4935264fea53d2fb2e19b549c8c69d45635c909bbdc9293f6eadc0de64c13d0b5939a6d30ece9484308347219106c5c646d0acfca7207668b038bd50180b61b8056c84f0a1e9f02cb3c95aa6e208bad3202ff39cdb933c64304f252f3070862921ea6171369faf4e1a57dd6b431722cc2ca517460269450d329dcaf0e0632005302351ac362018aa42e622f9166378a1621686f8db050e9fda066668a90c109941966eeeb5a982d73a8310a9c0d88aa6a09799a2b0bfc4489b90549677847f252e22af290979ced1755ced6fd97b6498957c1120d190368b9f01e6559c1e8ec3663b529c2c3e09ad053612e663a5628a2ea6fc046b418bf231d5d7c57365a9e28968b04330f9b2647f67834c96800dc9e19b92e694dd7968a4d12359d1e96cbd7fc934610eb172029c0f36955962754715a89550884fb77a2911e21c87838d1bf987a6f84754e4914f67bf269e984281e56a32b2e251a9e1af07312233ab9c3843c2f58c4e05db31c8dff88fb5e088704327473a200bda42e1fa43fa672823c488b31dd90e21896515c6654dfd7ac361474398be6e9cacd45c2c7b3b8dded4c60fe083ccce9d9e22418b89fd868913affb144a840ffb5e9ca97281cede2c1661ebbb144a17c7f0bace85fdee8dbfb45657b5dd8f21eea3c3bda57a32b7f1f0c5dc1d7a03909447e1a54796883c3582f9ed17a00a1fea0aad9a7ee391d88f85578a908de1885391048af06f425ea0c3eca24df0b501152a220f69e43c099ecaaa55c249bdefd30c84f0fbb23ea3b14d1db6206caa1483591aabf5919cd0779014204ba25d538e33f526d6e065cb00d3a947e1076142f15390f047b264e23183bbfe9bb917a4d317460aa2a5974d124058a2274eb2132374dbc079b61484081a91de0476d2f0801817119817edbb294af05a7645705365702b0334a46a607e7bc4c69ebb976980131a1cb0889757ac12fad530929d250feffdf23b927d00e022389d44a62df3f593cc1d9f4f17eba850e67a0770e8ff08fb6ed650e6f5e3789d2c31e50781e2b23135529b8a448bd1dc397cd0da31148fd17f04d88b01619fa3a437ad9bf0969bb27821a89704ce3e40c7c38b66ac38cfe13685f9fade91845f3d6b4ece6733cc16fd86e8e1bcabe5b6bf459e60fd4c5a757c07a53e2ec6efc5e6237f9d6c549f91414ebe6cb12d88cdc6a178115505a34b2a94524588c928f37db40f6bf7b37f4076ec3d1d09b2883f111ff71a1439d793c77cfb9f0c33ed45c1ceaddb20ae9a0432bcabd85ccd45ff0d6c04dc813a24e8681c893dfaf3e010d000f9df9025aadec73c008e3845f4fb0a1cb5dfb1ca9d1ff04721b6a46dc6aeef312b3055cbf9e1c6d289daf2ca40fdbf0ca1ad39c5a0dc480e27316f5edb6a8341aaa93e531357e8157e5b64b3c297d68632d83a064a4eb2049a3356dbdd662a115b2e1dba80f1221b3bf91e30c4c9617084d725b39c8da73be9d060bd488182cdec350f4c62de6fee4321624ab54de56ea9564923f51792ce7d955858792007717ab9e5a884ccf0deb87ff9de7f4b9a31480b44442b88304eb8853824fec1c85f3dc23ec7e30a4730b746d48a46788935211f60d7b0153db1c25f137b0cf33cd3d6c19bfb3ab0895ee3d8779797c46b3f1f595fb0f7c1dd3242a4afdbb1d8b06ce3443adaa36a8a8d15ccd8b338cf5d48c6c4d82a16d3efc30013efc7a3d7523a21251043205ad226d383b0a8b5b68a7e44cd337faab371a0dfc8cf6d538d4f5c082b36bc4e79e20ab01042f395808b76f19de364850f6a58a8937cf0f60fb01236ef16004d9c4f7724295e9912f591e21f0258176c830dd8a5054226dd17a950613a5850ae2dbf3c1a847ca85259268b184865732dbb0ff2ee9060b81a52fb81e4976c92f14cc42045ca049cbb9314db1bed0796396fe2c93ddb03a5689bee9d78a2526bf70fe98d50759962d13d93307521855e2052ee2428637aea59652be6a08bf701ae89bd03aebc697f936021e9900ff69c5fbdd1741c96c77e03aee3ee3304715d8942b1d697f97fcbac49ee8ffb620fe99aad2e6b7c1c85a5441a8d237dc069cc32917f8e84d91b092953a00309a36b6eabfa5e8f3d1fa9f32170700582463b551be6498094c1aa3c1350a1c24c889c8cd4ab5f043e0962f9038da8ea145f4d880c35e078588338b6bd29103ac3150e52c1dc8cede213c6609d04e51b37a468318860c7a43c9770b52b56d559581e1eacbb5c0152b131b9b181da220204076b1ae32ca3e28372fd8c9f4372698c84289b2be12408a17e8aa20c8a72288cc8961a3564e1cbff801a204f02555e1538d568b5a6a9810fe02acb427a7c5859a2bfcc0e08bba5d2a033ea234a92f119a228eb6f8bfc0b993b07eb2f68a475040e01bee8d7f6c792637ab252ec08be6b4b68662754d66d37cf6d05fa37e8858634550b51da33e330dc9bc67b7278e5185997ee8ea315934a081f8728bc10000fc36d6e3ca970e2f16a3d90fccd686ee421eb93677318e2600f2bd185e99946283dea0a002662f49e50b1682ede3096d6879e3114236a1b6d8482c082ee018ec3a7a28486b0e6ba5b14b12d36f522d38ff9f25611b8862ef9489c8b3038981b927b604bdd03b208d73bd723548bc2519591338eef661c1cba8ca2f4d3160bdeaa21dfaf440a4a742aa4eabbc8cc4c6938b991c5402afc7d9fa7a56c661b63170e42177325896c3fa2fba0865cb7306faca8bdc56b00c5feade2f30ffbe08e72bc11b260fc12b1ffae87a0a2a26fad64f7316ff578dd4dbdd9a99bdc60001828366a5916418f72ecc6b7a7adc8bbb5dad9bc1035f98552d7fa657c29e612d898cf07806815dec55c781460002edb6ac3a3060b310a663dd780600b0019363510114a203ead45a945920cec24a075f1fd00b3ac985d1d841d9c327d207938fe123091a38ef60b70ef480fe2ca167b287411a0e76d48b0ba3437772ead78059dfeb697eb128eb5b317df555b08bf1f4e7be234c0b43e210642364efbde5de5bca2453f008ef089c09417cfbd02aa8e30d9d8c63eca1715380dbd7b3bbb3bd0c4bbb596b619ee7d96de338ee529f39414aa90541109c370a285e7caec9662f537a003cd79644b5f3122848fa043c2f8182d983ec81db33a698acaa2643979f3550d949981aa8e4aa9f0d54d2191da281a6d34fef21d4d0d9f4230debe252a5f649979ebd1b10a69069187f59b419f119eb211a369ba961d37bec59c3e6f44d8cdb491a8847292c5461e75ab83e1fd88681091850f1ccb072d6151191273291b0cff418a29c48ddf07396f3c84c152a227e3a12b1239ca75bf6ac670d54cea379c4434668d03c3aaa324283e63c0ae2fa4f6f5a43f58c06f99021c6982d4c68aa70226d1ca520055d5c0942cb1139a99e9dd23a24c039320290de3ed3db3793f4f61baaeaad14f7e45cb9b334b261dd90cc3ce761776b654a7aceafc8f154a566a09ea35ec29cd5aae7bcecd853e7c41aab5031da51b58aa59e73b11fec90b825557acaf992a57aea5409ecba50fbad882a4b799655bd6e2273a5dd29409d2e5d74baf817b0323dbb35f3ec1d4d7a768e7dabecb4759ab8dddd6dd9ebac683a67cfc902167013974cd66db21e97b91432d8989f2a5a0c8762a8f71c4527e54a696d1aa452ba825ae90aea9561565a5d94c995759bacc7656e858c4e590e4685d7fd487d61155f14ac94d2d5ce14b7faacb369ddb8cef3587564c6315dc63ad3bec5ea6cf302d01b52d17bf0c66eb11b1e425b4c280a9b2d86d35d1d3f75f64386556733b8299b1bb029d80b5653ad8dca583d4e65bfda99bad4a98c466d1a8fab69aea41cfa20d54fdaba546fbfbcdcd170a7d7e85568a7d2dbe974a33a4c5cba8db48e3d40255748c55775efe7ef74707439ea69f5704534eeb81cf5763af123e7e7d88371f417a5e3b1a8f7b215930e8b3be7f8a58ed73e76e4f6e97e385803f5d390b5e496e1d307d53c15b70767d8c5833d297765dd308e762e5860f154f41847bb47e572547301724f39a99e7242406c1ed4498083bad1b60f8d22f4e175b77763f51e1893a54155c641bdc372cb290b52a4a8562345a2a6d32a3aa0619406b92a0f1e3a74dcb8d1336525cf97533699aa081567055c3171673371a7f3bc94524a5f1f3c1dcb2150dd954be99c8ed374a7f7a0ae86d12bc42defd34befbcc2715cadb5729b57eee15008634f7d3a7d51ca4dc562b11578603a68377efe08fda4dc500d54f6ec27ad81d8a7f7d39cc197e1ec190dda7c7a33d1a0eab3fa6a846db4d487a90fd8623999a6a55bc09286f33527a8445dc883200d549f3a0931a8a7524fa766ed1bedf4699a2fc35a43606e0c6febe9eb83fa327aea2114ea40a8ff0805c162b1d80a54b118498bc5be4a01720f08e3a1061947f5b23dce519d7790c03e8c96ae6723921dc643f3f6d8cd6e7b8faed4bba74e2be03ce24ea76bd3813be70c774e27f706f8e472cd8633ef59f15cb341eab96603d4b373cf351b687f9f6b4cbafcf4f00260ffe6a3d21228072a5dae067af1d7327ef310caddfc039b071932fdc57141226d08078a1c71fa6ddb98cc6fdb5635635c4f17193468882bf69b874e839884fd08fdb605a13e9b47fde63f9b0b6dee9bc3b607e18728905bba7325c217019bc2e79e6f7e69500ad63bdf1cfc2cf5713bf2277aa315bba9d4dd9d015128b23f9d7b16198a5252c368947f499374a48c39c194269c44b144b653291ac45d3ced5003963449c07042b6531a85ead6a8893dfbd8cd2e7b8ffe009d5edaeae5f5d2d9cc6f54dd58dee8f9e6c69267a36ad9397a23539f52c7f778e34b1d9386520ad074ad35a0ac86b0158149d0e7ea842153df0eab1ab249519f76d94bc8e7a187e311ee1b372aead3ab1146601ae4734fb4d467e4d0ad11b9f9c6297d4fe7943a68062d168bad304403d1b82e0dc435264f75aae4a87c77b49a9231bee498943c7dc93d7d7350df5cd4971c142df5eddb6490b2ffa8cf9b87a3a54242f7c6235bc9c94a2e566e54418ec0f187fa4cff4617f599541bd3469b7a33df6ea7f8f957569911e9892f219e7ffe8d472a12f559fa722651d1207ab72ad7f58d8a527d6f543488c9ad2a86546e545c12a5959cd28c63e294b8a599449f1ad64a502d3347d33a2b94c651718de385761bd7b5a777cd6d550632618ba72643ec3978ae29f1f2e0734d890d4cb3008df1de5cb4208303196986a891a5909dee1ac2c3922e62e0e1cb17723a1494174f5b66954f174d889145e1456a0b72ce90a50c2d9e62483489424e77715902fae93cdd569fd6d9ef0d0fb188001b487a84a1bc68410b52854e062148db08986eb540a911825481abad213b2e55635441aad065e142762337028cc89710eb9e7bd688ecc6d7089d7bf63582eddc1badabd02d51a5fb5a0b722ba25aeb484388bb8d08285d3f2f15addbf1881d8fb01266645842aa40ab203b3722c1863502e804b915519d3484b8948ea1963150dcb04992be3decad372d40ee2979012e2fc3cf59d5e0d960b058be123df0a007e825d3687036b4e17c90eb07bb8e72dd360bae56e1d7adba70ae8878300e0e6ceeb6400f74906e22088e421d4fca92a2e33a630feac33910d101edc3b90b8ae79c9bc1a579d093357637f0e0217ecead180694e7b7dd06822048043a282507b444d78acc0a5961af2010da88c181017237cf5ad060f12db7a28de7884755aad0b80183575272bc47afec8d387f85c3eac6412fcc1127d912a5e4b8cbe5355c23539f198ee464ce0884fa741c8b8608a461f63aaf6cc61e0de33cb8c1bb5dfed2b03f3a6e42fabcc50a99af37cf5bacace03b0767684123e6ebeac77c29393e57487ce7abe72d5666df39b8b14a113c38026918b7f2d18fe39f07bb01a301b65a2c160dfbd9d0412f07bc216b884cdae4884ce274a115f9bbaeeb4050a881a673365fd7e44b9e82a76e8f65cad478887ae18995f2c44aa95501254203aac8c23efccc3ef685aeb360d7cd1342cff3bc957fe2e7a068d97eee81111275ee75a3732c16ab7a5d188621cb57be0a576eb72f6eb882b1fad1d330aeb2c29015b25621087a6118dad0ce300c4317cfb7fabe958fd0ca6fab706421dd9e950fabf3a08337e14a9c1f861e86ddad42d079f0b3d65af6a6e7cdf9857415b244feb08c32aba732a4ec0803c170fee877bbb259fdd05985a0f316fc6e3e9c30fcf83dcfbdfbe5e4e8f084b6894e0ea0652deefc39c5fdabc5652a05141cdf0150909d450c9a685295c4130f3e90ed6cb7d9260674f63b77f0b398a81245b6084a41728d8913c8c8231c194b55a40aac841654948e742f205568fd40d2911b01ac050c104c243bf185136648ee8202309648900ba938936861a18a6cf1670812ec2cab34faa81ca450225be4718154a1e73c02e41ebb7d2e1eefd968d84c134bafb8397bde39524b79c44beb18b264b71d066b1f1a0456f168c6aa7cde5c58ed832293dd4763e266583c9ca6cb75dc8c27b7b4944a01375a41af41e1b86e04c7db3ed4614f7fc5825bf2930740b6e755152b47e056bf41ab6f0f8ab57da6c7511606178936b0a101cf6b47c7552387466b06cecdb561ad42f0f36cc76d95b614ea3dbae9c8ddddfd82eacd4ee7a4026edc302e6f58b6762ca7334ffff6f9d35d6a387a17e75c12d473dee2409bcedbbb9bd61d2814d53a92c0fe2c4261fa8bbfe7c401e3965787875e1fb47b8e82732b9fce390f0fad4cf8bc9d73ce5d1af010cbcb9ee75827cceffba0b801ebdb59acf7074506d8f8761b361e6cda9a197b509c59fe135f1f2ce0db17b08007d98ef35fddd85f7fdb0232df8030a50ced58b8257709ba43ba4ff0ed30333f1fc8dc61bd1d041b64ff793ffff4528399037ac90a0abde49e7df210e7b6f3b27e38c228acc697b01a4fdf470f19d70173c7e6edf3631c9c83f34ea49c63631f8ee7392f19c0ad9e1bcb1fe61c9f33e728ad77ee8086f57fa31091856b4714fc39ff41803f81d7a8f9ed4739be88c20973cde745fd2730e0bf71328ee99cd80fd6d08af547e0fa22d76fceb4fe6e24c16d90200d6be187909c4ffb5dc98d42e6e880cdb91dd04193dc605e7766b63c9179def2d4e54ba12d4f4a5fc3d353efefc9d178e3aa73c3b69f15db152ed76a80e24b1d3c5a4807f5929d739045b1d95b3cab07cca7a3c8c265e75caed1355ee7780805a63d09502081fdcd8bb8efe7a8cfe74406fc369ec06bd67c1d4158823273f0f7686f8721893aaacce496d6bb851a0d49b086f10b7a0ca5944ea73da659b0f926b56d4e29a5fd85524abbc7504a698bf969a91e4a296d312d4a29ed31422e293ae7a494523a9b8ae17183f4b4030f30b32494c8b2cd6655956971a14a6a6a4a0a4dad810618269a4dc6258ba39f5e448b284b814f1891c452b5c110691da0862e59106931c1cda652940fdc4c8ae36e2c16f352b4cdcd63b19897a2cec662b1a20ecc44ea6a54783191c22f53befc4024e469a0a2cab43a3589f0d407df972960befcb83af832058c8b084ca70b1643c03e1a535143c1476b019db5952198d0d90f8d295f7ef829eaa20e43574494d6392b0c733a097c6edbf43e014f3314004bf09b79f43193d01a466b994d35f2d33ead540d33fa49e9c8d30b48559b61ea07f05c73f205a8695b70aa073da1b3b445109c39aa53af28b4d36a1b16d229bcd5eaadeb7123dd38f690fd368c568e993ff03afe172005c11e4d60eff2f38ec5b29e8d377e2211d1047e960df368423b7be753e438aef5f36dfc441338e7f7c2d0ae44b65ea7b3fc7b502245df26f07fe32b84af23110a72e0e6d337e7baaf8e26b0f3b66dee7d2347bd0ffaa7ddc4522c3d08b05f6e55fdaec63b32378cab62f9b54fb1fd134b2245cf44c26f13d8b9f2ce741eab09ecfce0bfeaf841bbbe646f6757fad0801f8d899b61c169ba74b6999a0d69c4163898d93c432d65a1b5721c0d55744add045fbdd6f905b86d63abb6d3af7d2d57b7572d4f68dfed8d3f0d136a981d75346c7ae7610ccabb228b6e16849eecc7821092932a5bdc90c64c17275e3a043f1c71e4c41549c83085ebb9e6e4889ff15c73b21405b496991b6d68ad738fdda132cf4bc76dab3c5ef661afb56edbb6b9a0769e1fe1d95fdbdedab39d1228a880dcb37d9f77b57eeec15c97b7104cc09fcbd57305812f040cf43c6742577b1582099ef3aeb9160d775615800c63d8d679c90314c29e6f3cfb0d214f7b604358a2f790f094c74b78ea3c1c78da727de0a9b7883c0da90d8b9e7246ab5963f0bc157c37edd130201e58303a485d4ccf43a29ad1828e1d4fa0194384145d8cb04296ae77e1087461c90a4659ae54f9612a298a1cc2f86c8f07401cd0f32d04fbfe6902c6e561684db27c8de75a9325af0770a7aa75435b693f51426ff4f28ecdcb5abbba18ce1cb384d6b66d61156bd1f6b45ede38e78bb6a74230acc7e7c653b75a6b756d636bab1c477d736e9bd41b5647cb2da9d54614b74b5adee9a59de36d585b1710b889d7f257cb9cdbdb47159c23ec7f7e0ab5e89c1da5455c6f95735a044f1ec39b65165525d88c77468a106c460a046d5a7776df1f6125aa1c73dc6c3a7ba9a9cc96e8104cb3234fc7568ae242764b7bf62f9ceca1d2b3a3a9aa4a83264d159aaa46a282eaf2d4250aea090a0aa928da9c497369f2192a3455542580938710d14f77a59b525a6bddb68dabdbc6715cd775d65acfebacf53ceffb3e1004c30f04c3305cad562c16cbc666c562d9d8d8dc7b6f6e6e70eecd0d0e0ece8c19335aad160d5b5aa290c856a26e11e9541d1dda5387cba9f12db21b2009fad1beb72daa0822b020f1829a30a45195355e40b1c044124a8ec8f2e7b7cd81d0a0cdb9c67e73a10662f29b7bd040347e8ba10cd3115496c898e10ba32866a0a4d1420a1830c103b9d55abf59c3fa1b625bfaf6ae05b7f46f4f8a71e070cbeb49f190f88de4a9370ff5d3ff5c2062c6f1cc38ba2b9783222edb9f5a7ccf17b0d495f22121093f44ea963ff6e7c7fed81fefebc05a6bd9d9b2540586b017ac055311db6000d93798cccc4c99a03acfd61b4b0c88de4df6b1cd4e9a827a9e75ac6ae967678c3b9da3ba609269d006a35efe5467d1753b11e69c28f4e38d1187509f20d46792de983b7df6aceaa91ff0307951a6c79b5eac9d4bea1836ac15ded695262e7cd8b526488eea770facbb256a7bdc56e914e201c4248de73dd7988011031b52ddb2d54044845cb488fe0738519860f2d3ab4844e8c17cd6760f9ecb13bd615e377a56406b6d0e1c0f7387dfb12422049be3b7e3d678ae3121d369b999138b98ef99ce8dd2cd689c4e0d6b1b64a7b74f280d54d430a50e32d23eed3b4ee01cededd3061e1201e368b71f7645732c227581d0206f9f766bef8502451e9a3ecea09ee4041a276cbaefece0fcf412e74b9fdf4f2fbf2f9d839f5e72f0a5bff760af7ed077c6d13369d6c03cb4cd56692acdd9649a4a5b969f1e3f34d0bf929b3dc7b4d166505f6e491bd226db903624a4b925fd44da6661a484ce2fa463643806a13ed33b4072992c81faccc96548263f2f796a84d0bff115e473d099e45590707c3555c3a6d44f276ed952964984d2404a34c832f592ebbacbcedc4e4d251ac4e4d28c89f63da33ed3b5cc6c285244f5848740d0b4f165e40857d5c043d4414084cb7ca58ac5be8eaf193be232df2297f95813a424482d933529a7ec35931a06a561d3a79dfd744bb35076c604d2bc762cecdcd8d33eeddbc8ed039633be79689637cf227bb70bb53b6c8e2ee5aeae5cdbb05603720ff768e7573b1bad30050b14588144062b2041e22cad71618a0b25514690ecd705c436793210fb9c45da8d94d0403cbe6a08e5b6dba5bb25f15002be690638e806fbb4d3a4f2c65ce1fb698cdb801c1ce4ecc3eee59905df6510034868587b0d9a5fc43e50b4a1f99e341f98f0d4168392f42557f970f4e53c9a48e594550a543299573bf9ee2637bea78ab41b692fe1034d4203b11f6047adc9cd79e8073f0a2ec651fd868b02427dc8b265b46938c1d4529f29be84ccea9cf812c2d5b2883e75119a742adade1310b4997af6263345a6cf9869a986b177dd78709df5babbbb44758122aacb2423c5e18a65d34d65466588191527a6341d4bd347693a095255541728a2ba9091829a4a506694096552994b7d060d159f4953d53c9e6a17289b7b736f7066f8f0de1bc6de59cffb3e100c3f300c572b16cb66c5b2b1b9f7e606e7dee0e0cc98d16ad198d1343aa76bb872dad53abdb313f68ba7f2f0d09e3caf904516cbcb3678830d4276b18a22ed29ee842d96618b3c618b1aac7ecc6fd1c6ea878db0c50d5cb4a74f6a831b461d64d774d769edd8bc2ccfe559f9f05e83296cdc0d36709f6eb9dcd2ba36e099d3e55c6ec9e5b283ee95babb4f9fb6c31bb2f768cb73ac4c8c837d4719e739997d12597b03820f68e6fb97d7fefc940c3e3fe980829f6379b9e725a4c0f8f2e7d981f46007c2fe23a485d9a5c36d671d2e7d5e116d1d38c4fa4001473c79cebb0316ee7d5ee24bec5f60af7edcf75cbc1f276ee22a0c7329141043a158bc0775203abac51f51a861b434f2ea2203461d088f3d80d019cfb52525de05757f7c78ea3a7e9ca903a1fe23c489e1218a07a81e98bef4d9c3ece7df207a05e117428254aca2e83a49419e3a8be58fd22dfd87d020f6c9791ccc1be645d5791504f4e9451a367dabe1d2757d33b8ec3b600e9743174ffd6c0f55703767be3ea9b3ff70a57fc94ee4e737e7b1a371f6c8912a424207c723a087e3915005aad24829480fdaf126726512149924f2f3f36bbb04d8a2d96ef68e9d63df2a6dd1babbbb45bbd367d91da1810d271ea797d38e938e93cba986538e938d0d9c729c6838b59c6638e138dd386de04e374ed7c9c689e5b47272d169e5143a814e9f93383a7d4e9e93751a01e0649d3a2700f438754e3d01700a0007d4298d235a37aeb3de07862b96cdbdc199d1a29153c3a5b3f3e2d1c046134fd3ab69a749a7c9d554a329a7c9c6064d394d349a5a4d339a709a6e9a36f0a69ba6db64d3c46a5a35b9d8b46a0a9bc0a6af491c9bbe26afc9368d0068b24d5d13007a9abaa69e00340580038f860bdf6c5a37aeb3de07862b96cdbdc199d1a29153c3a5b3f3b2b1c19452acce6c68c0f3dad1897dbbcb450da84c698b9a72d5c8a1d1daa2be7d8638f228cdc0b9b93623005e328eaa6936ac5508809e1d5947a30ac1ae27003ad3d977a68f53e993058003d74fbd375bf9c8796fc638da5b46dce9a5c7418defe0e832e330e20a20e7d9415858673c64c3f358cc38c255085f8cc69528503c2ff15c8b92c486f53c64e365f85ddacb9fa7f988d74bf0f97b16e2d140349cbd4703bd5c359c7d88071d04a33e3c34c3d93d1ec271e66ce586959c4fefaab35375764cb05e8bfcac5567e43167865b725490128a8839428690c0810f3490cbdb8934500d77bd5ade725ef9b0ee79cbf37cf3dbf25abe596fbef597688275eabdfac08a9b839eb76639ff045e53549fbeba4328d7759c7942461392138ca1831112621c552dcd20862cba90ed4186581a6487739dced9d220170d428360f435ee8cdce88d1b882e714269989238a3d010674b9c47a210c6d18e234e8071b4df80f9f61be6db6d44661c2c31dfbe1af3ed6114647eca6034c802ecd39c864b185cf015dfde5f6e69e4375aa69ba0e7f90f7ecf8d36b10c7feb80855b8b72f46087dda4de15d1e7c659c5ccc15275ee4c969ad78bc79708d5371fa153a822847a6f9e8233599d49beb4677710b5fa7daee910e599812aac89275442d4ae9421250164b4c0420a7a8832c60559ce30194208272c2081b951907941131e920843071734f1c41349b848a9610c2334d470c593253f342929e0881a827c8099b50ffb00c3d4307e9fd3612d7bb7e8f0c41214539cbcb085acb76148eee4a2fcf42b91d389da5eb965c8ddf3470dc83d411e039e7a79f35c3807fb50970eb763314fbd5c3dfb94089b0f9bc46d1dae2d6d6c78e8e7725001d887ba8e0e0ecef7658ea7aca7391807e592c33a39798a62cd122eb0f20027846821ca89345864d093322d2de578ae4919e3719e6b2a2863d21a0804ae37613ed36c4929e9e7943552c3a611d717a114898e93991cc0189106881590db886473b24295cc8b16b8f00559c712c492a7be4737932f6129f89269327cc952b22f996ac9971349c9c7f0e56432c241aa872b5cd4c678c9822c9d0a1596a82a5ebac8aac8526887091da65e903d216506241da7142e9aa2f030554613d963c96612b003172b9021031aa460218d8eb43439a24c0c5564b090e54c02e24c11656267b620b1429673f63389110f5b7688010cc0c0018d12520c630b1800110412515c207d3c9111ab2931431213c9cdd0454d0c35637c7042b2c64802c60c3d6ce14316d2c8698ac674250c2cbec4d041196334e9b0c5122ac0819c3390806189082b98c4408a9c3e976890d10b6366556081a489531a72d2b8305aeeb2cfce2068185e3c410f4948c46c917d21a70d65ca8c4942892921b2e002fbe93cdd4e774d25b6d35d5c74e8a891f19ef2a3a25d56ba4bff9df9e9df9906ea59c3e610b4cbceab0945aa8a31bcb0400662cc5ee028283f5489d142104014e183fe9ce4c6d4c1d1dd64349be302292f489ee0e287353b57b03c3902ca0b4730b950685bd426b5d136a88faa2a81dc536eb36f7779b9d4594c3c34f4eddb96ebdfb37d93f1d0dcc00f5496402246460e515640e54c15573ca1427202755d56c40b6aca08824a88a617a6b024f1650b244a3421d1848620d2df3ebc22db9496da37d9963421505b0269498dcb0e3203f4d0430f538c5822c6096ab8de0310024a2c0a115cc8e00b18c040891b76a0a1d6248aed06842947950331c87285942e8eb60422531fefab0e564b7d2a110e94612a2c60c7f39ade737c89103af51156744ca18a90f6d5e7de370671ead3b9a42e1d53a8b2790ad443ef3185f695afc617e79e33b98d42da57e38b1b837cfee27ce5fd122174eae1dcd13d530f5d48574b7d3a1be672d648c33ad1457d3af73a0f97ee91b1a8619d7f33b83346053801237273ee29b728bba4e3d36b140fedf829fafcf4497e6e83389f1e049c0c75b439f3346cbaced06eb0873669380d457b9a1b6d5ba241d596f72be35872bfd76b84cfa98b522b5cc883a89f4b5d7488e1cb9e1ccc7899fa92c7cf9269b4b7a5069a3e7da3d240ad43775371517ad439e7b99500939bf7e88d8a088a9c5827694526ebdc66db926b5bfae9bc7946b4c31840ee2939d98f00fa37a630a65045c8f44ea1992df8a3e346247521d03f67ffc623557814f2b9ce78a4867f9f1f713908ba1159c488e8f2f086192b62844cd4b314cfb528c048a1460a5584d4f0e929b85e22b8bc7d488d309749707c75fe12327778bc4783acb3a374d98bd0a0975337425b4e7d2ed1a01ca75ece5851c3a891220d7be2aa489d0367c82356890093473baee32f91c91dff441d9f2ba297084624e8113022c1f1881179a48a101d077dc7851cd9a1c9991548703c726487266762a48ebfc4498247335edd8e8ef7689eb9b20e76d85175453aa887368f1a88675539a3d0d9144ef6a464cb97134949d24c9a4a5d650e898b35103f29913d75ee88aa808bcd498265324d020522c3ab5bbaec83f5451807753086cb5e3295f51f5c5d45dcfa2854145edd18644727c8d1110d9fe1472dc7717047e4711df1e52ed1edd2652ec243dd53df967868c753374283a65bd94da214741e17023acf78a44ae747da41ef46219fbfc6237347045d47fc1ce444fe1c91491a22932d91c919229338af203dbdde889ee788d7698836de1259a15731024c1e1dd9f8ca8f58af20a0478049d047a064b660b286fc9c57db18e4e808c9f5a3231b3f5a39e8472c3f0abd1aa13e345cb16cee125d894cf668a19f8651dfb15f2ed732e27a460b40eea9529d29dde8e64c72be89f68a94524a23c0e451fbf41ac0f971bcf158d65a2bdd2847ab4781986bad955e4eb41c57d65a79db782c3ff07cb3c59d4e89b888d899d68edaa6d43650093275f6c99672fd3c0bde2b46d3aee321f43db86d1b277a3f450c88dc175aa933a515c817243623be7c795b9dabf75bd87515cd9755183a55ef033d6ff2101202bf073fef6b0834040c88be6279de9c348461b15644b47605ef5c3d80f3cec3550826a8e3a534c01a16c6e67ade9c2808736f3c6fce4d863037389e37270d61706678de9c348499d175cc5aad4228c17fed5a734ecf9b9386303442d68a0504c83d254ff0d9c12ff989879010fd9e524a69ad9ef7936b2dc137ceeaf8f9099d9be774ea80864d0f6e3cc13acb6d2edc1e77eb6db5eaf2b85cde575bf70b6dbf655ac7fa6018320f79c03f3888e34621ecc4b7b32512d2cee0e7852153855e744c4b90886bdb5d8526460ced092beeeab9467ba24feef75ca319f153a35981816db54a66545c9be71a4da981b8f7b94693d190baeee3d8dea92f5d31eb2aedd7441218a29c647824a9599217a054b0040625254cde0525fbe3c50d6daf0d6f12a9245349cc5c57982731d06280bab51f96c4da87ff5f406a1ffed25e297b5ddb732d0b93b5c142975cd773ed8630d3bbc9f52268977bd3031d7383932ac30d2f6071433594ea8260cc2e6e0d3253cdb5cf352850541a2eebb90665891840a135204bb3bdade79ad2125f9f6b4964f1a5cb07b09c3ffdc10e27f5e982e1cc17b5c7745607b9ea53ac953e58697757313620f310bbc838d83dcb4dcee5ba979bfa74b9eead75a4b4e956e79cb39b99997396dcd969b9dc7d3dd0596fc6d2a86706356983ea96364f7d48439dbb01618a53c3489f3bbc67777d60638b3be3994b1a2e4cb0be6147b1cf341de49add51b1580c095936d4b7b753cbbe3bf42e1bcab7779214df6527f911bae19b87d2971c55836692324566f0ddddadb4d4c47739a9e4f0136a76692f2714dfce53627c370e5fce2adb59dc10440e4da8ac210309982b3a8a2fa8b058c1821223c874abf9420caa2a29a7318220998a2bb82079411661602003b29c4c4f684e54506112660af2883440e040451926a688822c2715104d645e284181171c66a40e2ecc344171c28a305bbe167cd9fa8fe779beecf96f06fff9f7f995f29fbb9008e3bf249480ede8e043f7564461f3f47c79d06b03890fba5d1181b0ef69d8e471b18ec0d060503f514252ede303cc54fb38c1f09e7bdead88bca01c61b4c448300c2a1a696f77c4d277be79ec6b074680f11bf753197e698db8a2e3a9cf2eb7743dfd741a4e447d47ad7600419e4debc6750be68c12b29379e42f57205951f3c21251c630da610aa4265c68a2a6852886c2c511373b5078a88d40f2d479ac06ba903561841451bc28c15b8c61c31449b4600a599ab6812a0a16bfc6262b9efa8eed42e69a3a3895363d41c65377dd2bbe084363d6f0c224e5a9e7d020d50316301011234389d10d6b909072f002c30a54e4132fd4e0f0108df9a508173cf5169d92544412468ca9a10819499f7690e254a5c915332491b483a6522c167b81a494d6f094524a29a5142700fd4402b8e4f1cd4f7fe5866f26ae6031040c434c41c4ecdb7f88d0a28625cbd78a4801518361e59b40b776f1d46d5644d4b2e09661d9e2f9e6f2c633400becdbed0c06dfee0a2288efa820aef81662f62dc475999d8047734f5660c0f3eda11461701308ac5aac2c3dbdb06ab9f6a443142b202e6f8ae9ad889aa84e314fbd52efbe007185836fe75644dd45ed6e1c65c553af4c45bcc005287ce0628d1259de0428295972a44353194e644f20d4a793bce001ecb25cd24e257e7ceab4d56ab731b0e1bb0a19528213770888004fab30794ac52d43174f8d8a98a1efa9f187da0f4ccfb51f96be04b3fcf41bcfb52969405a27f44951bbb9abe7b6acd465de68d467ba9db1cf74fea4e82775bffca47e7663df803085caeca99642724b0cacf9ea2c7c3d2a5b0aa7eabe3a073bda549369a9a8eb521b8d06d920d8677a1394123f9d09a99f341ac42454d44bd919e320a27e551d34a1006349c0a4068ad2405c9bd2a4d401ca7ef67cf955814a3f4b7029244b70966449366bc25403ed09940d51503accd4d19685aa81ca0ecdac6aa0d25a792b3c14a581da49f84338f574d1f1e5542253e60c0d5f4e343f67f872a6f97bfdba57f5c5ec4ceaa777681aa8abb2b1aeaaabeaaaba2a1bb34856d65505a109a8442d1755410b15aa190900000a5315003020100e0904028138301e8a9bf914000b80963c6e469f8aa34912e3280a8220638c310a10420c41c698192a1a07002f751e92c83a4bdaeb979eedb5c708fb48d241c945e54ff49a1368c555fe9f742d849a58b1a11da42ee0ab66e675d3e580d841d6e342d7e06f0ac052d05c128f187ee149acf001e939d7c0db5a7cf9c22e8cae595a53820d13c1d4ce347149e9b9ab68093d4f168743d8dd9fcd3f15d603def3096f4ea0336509f3234030b4c376fff5f16710549d6390c509dd7e5d589399562fdcf90f9acfab43dc511132a36a6168667b17fe565a9039888236966a7969c13c8208d21f24475183ef9496a1c112fb516b2439ae8afa7f4d8a6c68ab35c4f1d9f68e29e055b6be3027017cf18a1f36228f4fa9025b71919e82ea4c029ace74046dbd4921836633a2db83685c538024216c48eaa957f6a79768d966090df8d5ba1e60d2feb63f5b6052904069108e5d6bdda57810a1fbeabd8e532805ecabd8fb32e20468845f083f7fd9ec92b0b47b64e3772b6da6767fb8fbcb18f7460bc2bf80335b1c99722a04bf300ab1086a1752dbfb8aaeac7151deec0a33f02f331ee5877d3f3e830427fd88f01b7bb779bf5b0e193d1c32d173c58dca1c034ab7742c9d2723105c3409d88606224c12878706d1abcdfafac729d918dd39568fca1c61a247170d6d7b9157749d0d53d220a7d28895304d0622ba35fe5a6fc37576dc8f9c8294a3b94e95383d7fa980c3c9161af4765dda74d17fb8d6b346f1d4c5325d7c75987b656bd57a341b36c92d9966e23a5850c3aa83e5f7a985e8aca18fefc812fe15428f93ae569933902a44ce9ed909c6263a65beee84243a7298791958088501470b7e590ade2a9f25380a6a1b4281d45620557137b181695f582d42db0ef0ceba76bd72ffcf81018a64df9b1481421d79d561b4d7f4bf79c0cf3df50261cc1d2a0bee00705382351809414e8d44bd1f2567673a601cd7b2898206b642b0e113a996fb10596ec0ce7c4f9054e8059fb4ecba2151915ddceea8df6c25923e1d518642c09aea47f4ed5ec6311cce2e94037c16ed15078025b2ca351481b1965cc6b20b79a8870d65d310c6e6721bdd780a73a5d5ae6beba6f4f8d3123a52c39139d46d9d22d2a38444c1e08c428bde7b011c37c3459afd1d6774c9ff5107200f3a55ef5b6da55020decebab204c702ab964c0c4fa674bbf278e25d65f07b9f31fe8b65546461db0469a9743352447165b2d847deecfb74926530a6e434bb3575e4daf00d7e5d886fefbc4ca678483c4565e38096abd6d565dc1af2afac32646538b06b4bfe02c0cfa8059dae901bf1952ac666a9c05428b7c550ca2ccefd484516ab69771965a1459445033b37cfe1320d49376722e6b340c3c3ed81cc4c38fdfb7da2e873a351ecc144de01703523d4cf420a0d218d4e0141b5bd2b99e1ddf4dc9b4539165fc927204b1ff38af237a8d8b7ea2d743c498fb01c2711bf3adef2c7c48d0f4a82ab86538a273828884dddb63afed1e02bf8f774a752258f99616293844d9de934009d50747990182676fa1e9d85ee4243531ca5ae6dbd5d4298372b4a7483930715f81da5491e1624fb4901098045f290e07594216bbb807cef25f26fdf1ea5e91ed32938af08222401bd3fcec42122052503a372e11e91eadb9f5502d3f50138c6000a847b02cdd358ebb6bb0c0b0f5ab736226635c25f060c8b42c5ba9492fe26b551cb677550410921a29801b482e1c21642068b6b6a76807657224b247db3edf84499ca04f4e045a70fcf22e1f38ce3f34e67776c77f969ba20ca286b59539cc61257705483f413ae9e80ac709a9ac5d844bc979900231088e68ce3f60fa90af847318b40f717c7a44f29c93697cdfc98cf96c9856def347ca83b1b2838888258004218f76066c24106358460ec284d320785ca03d5d80f05c90c38f8541d0f1121d648f27aea44210d55e7d9a8724c54d19e4c43d2edad0a4663ed076eef05ae0aae06acfa354cc8a03ce5cf74f1d8e3223b43ed05e52e1005f11231b9473f5b966747545962d2b1b10929134bb1cea8a6952236101348602d7af94512ab33db9d297e1428cd3c39b866a7d6e693e3daa068e41218cf7af0d4a09006be841e60cee060ef943e4a35a5da3b06c0bb2286a861a8649b281405dde4fed8e09521bf3dc52953ec3cee2cae148ecbcfdf6391f935af0cc53ab78f1dd50692e4dcc1edc51db10ee1364ac822a772f39a1a237625b3a1e426117274e595832351357ac507a3acb1931b6b8c7eaa4b0e427236aecbd555d548ae1168bbb7a3e28e8a6b6442096e0dd543d2a8cfcf557acdbcecf6b26816059543feeb2e6c11832cbd20a1fe77d409f681c1a9317e25a52083e52a1111887da780bdbf0279b532288c109b1e91418e4effa995e2f4414580a2627eefbb883bfb8fd6789d027f5d1d48c4e6bf46bc758e1d1c7c0082dc6ae534e1717708910ad9f7583cf0393174ff4186548955ae0590d7f7541566008a814450120ca1cc6743416b45e6fa5aba009e4babd733c3ed612293d4155d1fa41cd86bf7d4da53caa46067a259f20aaf17d60efcd1e5b246d783696f5741ba4e5c30c398bc90657abe5445e91b2ec2b540b9b557f302c1b9d6b5dd483d6d98f80b39d83346f6a73f7b764e4b555bcd7a629faea5d40e85a8a0040529842a1b578fac2fbfe9b01de7803c1e0d4eaff3c0efff295f88682655abdc0e6408ac274a68d9fede0fd447bcbc68bdb8fca78ca1ced5e4f058e0c2ed71cff05b092189a3b464830a0f30d6692cf54962cfd0cd6310fa4bd5665fb1660e906fd142f12e3282c977b37938388e17c68e7b4fe97629e9f9750efd65cdcb149308065fae520153ecc548a1e3c505134ef47252d3dceec0d890a01ef3bb0b68590c5018c8b81032ea8220d00001d2fe29ee451cd1b1548be5007f0ceb3a5b589fe850d40884d13d01631ce5cc5ef21cc80f8996c283c6d5e676ea936fc4160f42a84202dcb382be863ec1aebe9a8ce68d356f0cfd45027ccd370f4334cd07cab5e26b614109ec257748b2de36620adbc0ab40c57dc516a8532e1527a8fc63c177770b0382ba8464665b8acfcbff22874b873cd241f82fde52c9d19aada7c3b9ec488b1512efe0b572edc0b9a7dc4c692f3459fd5450b8299bcc30054d5d20557d294054121d715f8e655e09716740ec6720f7117c46b8c5e64aebfa06eab62df1a55865c20a09ffda1c23a31bc37d7dd733d1506d6da4325e3c8c2bacd6d26f33ef3604dcf47dc3d219dc305e1fcc8d447529b66cc15fd06b7e195ce69834ec9f942e6ef6fefa66ca245f8006a192d23b6483a73957b7f1427d9b7ce20c79a7dd4d90ec692999d8326ad0b7bea4061341ebacc0e3b82662dddb8077fc34d3b4f3a9d13279e064d26d5ad922b0fc22ae10ba73ba167280769722fe99ed6d6ec34536621fbbf4dd2a3c667b8311f3c66810d128206c952efab12ee5112540dcf36d932c16b8813fbf2c76deb661d26a387ca21ea251981c532ace274402b50b57d181ed1204f3aaa45f778b122081722c88d746235b4603544f0e2fd273ad38291f49e8343c37b66abf5f49ac3f649dc18a5417f7f07256729c7b8c3d1e6869c6f2aa034216c061c7a4d55fae0f93eeea96d0bb379f9855d95f082ee75b8e050b26f673612451e6781a36ee238e81c1396ef6594f18077d16fa18f77165b7a5f73b8cbb9d4f2942ac992b8afa5bcfbca9c55efb0f7e90d5a64fb6af9f1a8c10c61e3182f2c9a56dda598bc4b3961ece06a93ddfd0f54904d0dd7e2c8ff6b5fa2dc806937cda5bb413b825cadac9ae47991acead6798bd872869c98c54a96ad29d788f9c6b7a2d642911bd3ea38e44c286bcb5c40b25ce4facea0749c2b9c3a2e5bae98a20f3263731e2234e96081ff51193a8571715f5dbe3cfc5fcca0a6e35bf9af36582a4ddcdc36691f88a9272a41bb222905435be7aea2e085d9ee1a2d9d3147ac5b5419c8abcee8f760a8f1a8534fe9dd0e24479f08bbf5d270b72a5a3ea8fe6842182cdd46fe3b0100430f100deea5dcaf197923114b6b60ca47fb9b78d148d540587317ce22624dffef3d1d3185c004bab92c54cc574a7be1c3ca2d7461bc2eabc1735e0c394af641776a1d5c5900ff16aa73bc4751818bbcbae21fab19ebe20a1226278ff4abd83d98a91066d34c989177cb47ec613fa93a4ae258a08398ae32d7570b7e5b0b2684ab050f3727ee7f737661e3f96a521d7ecfabb6c639fb2ce85da3aee32136878227f140e767e71cac7e5de07bde32d03f18df727c0fcfdd655bd466c73c7a9ff7c85411885191786bdecfd10dd783af82fdf8db0a6ba5efe842b0d556509e5e90e7bb9632358f16c5924a92351d9d9011d4a7b90a10b2e3e7382f05e237c6c14f7650426b3e79a4c224f4719bcb09727c9da23076886287f93563f1bc799ff05b78c82563bea62d66f6933ec8310246c7a53d191a39a549f33c5763851dd944108616acae6c23f5f5c6132331b86d74042e06afbb49e72a0bdca601ae6a519f469a1045266c2993173c1807d638cb92b11f4fae0600e731825cec06120e0b805270a24ae970526d8d5be1da73d49bd6f68cab6ff9474ab15eb0518893624d3b04c9a4c6fb0200b24dfc1f66e7e08fa95c1967808805c51a627004d9518cd57b0888508d9058a8ae7d2e0ed93566320d26aaf5264d3ac114c2ce802c3211be2ab9f7f1b6003216854a6061987b37bab82a48b68467059de3c0e2d5c4b04dceda998193176d1c6f338e3d481fbb009b97976b50eec2b26fecd7ec6d760427fc5379c5ab14003d8152340c5caa45b92ac71d1d9ed62a24d490ed8dd4e14ddbddb450a1a0546096a1e46321bed216e7cbae70a5d2d61662c486812100909d7f8e41db1aa3684c64b7efd66f34b95fec04cc2688343721bc29963ddf260d93b06c3dc9511acbadc18c734d39b8568f2db327db2b0d04d3bf828217d156634957362b28b61754cfb2675084af8a0a7283d35724b373f378a8439d522e231d322292324def12b777f51969f1a0484ead82025b2c128fead96bac337ed7c4bfd12a2bac9b0bd99fad605977b2d8661d4a88848db87e2305a2b7a1d93c66204ce4b626256866cc938a255f109fa1dcde9a82eac1567f3ac83a1732060e8f19669c1ff1013761050ff9d49c4d4672c5aaf857680785c48a078390dafb07b0895650a0dbe0b40aa56f827861e5b2669e5db0c2158f144f383dd10a1254c3f3a70ef206dcf06f876b845c1cd6b6c3c12a12eed5a4067325a70d76ee02ec8821b76bf54a37dca496f2bc21e0117afd6cf2de6bbbe57c3f29464409c8a9fab622e0cb9bd0d287150f29e028dae40fb0ce96be529cd331d0770ffed5630042d631cd81941e02c8016bab89575032e2a81415a4e58db75b57ef52ab981c38d10412ade7687bc14720e843e462c6db92042342900290d60d2c363986ee644bab407f38b0181a9cd308d8d33476494ec8c04bc6fb38bc8ec300ec05be418eee20964cd4090cf1f406fc7cde7551168a419b97d9a7d5aef3c1297e96b3a8189141168af7a9a2ba01eb11530537f7b10b90271b95c45bfe1caecc58480ec1d20b868dba5815a992134700e0f0d1ed51ea44fc1c51b2efdab16cbae5b64be156765e033988eec5697706b8dde1e87aa7500829df9b0f264573fd8f5500d609986307329a7a8e565aafc8748f5168cb729ebd80c7ba0a12d5fcd35cda6b6d0e35bbfe7f95bc86ee8853f84a6f395562ddb5edcfe57a8438b8ca2b69fc7fc73693f2256d678cf0147f9ebc891555f6b80a86f9c2acf8cfbf586ec395e5b820952fc9404a1e12cf3e166b0160677059bb10d278926fe0fc47e4e9a15b8b3996d17c966c5a785d9a70a51a562fa611c7150ef10102099e2528d89eaeb2ca8710c92059109bc4c409e6f65301045d7d5e80faceb0432ef4d200084459b1a88d3514804aba52875db457128b23cd8716707f3247788c55e0c710e0f1142ececf59650d6418ef9610a4f0c168b4bf39b3b51dfa611bc0cf51a507eb824b0499fc36d1f856bef2372cee5866e0f6819dc776c7d49e6da2889f4e83942db9d346b687e5d0cb4aa9b113f7aac1651ad6f98ea1a6965064c36360a4fcb88a3ab6ab82d2aa0843c626dded97ae744c75f9f33113072206c3517b982a7cb6d04ea12c051fe6d2026c654a5de7f162b361f18a2816d5b61b572e232fad55943ae3909a81bac56f8f7e09ab9322e66a8c29e8340706bb6ce4c188d86a7b03c6271ddf9bb6cd912ff429ffdfd28ddd2b032bb90d3d6382bafe633126763a621384a1468b3bf90174e7e1f02c9184a96a883a0b4f8fb56722f6c743515574937bb29c146ce50ee94e704ca34f4eb411148f17d2c81ca352c6248064786bb5f420d0b1e845ce8b9102fd16fc9c7210a39a0a42dcb10557f901604c5107c75149f43fde852657ad7179646001235d79685150ec052265f14171a4612e15c24e9ef9c5d140ca414a1786cd24c2a8350e64f0565646819b5e48a84818ad425d8890c443df7e4a5afd2740679f9871ec08394509091e9cb952f5f78c16810d5c2d670f8c71539348e2abb38d3e135f22b483084d3060a84c1c4b1aa6c3c2ffed6c660060f172c3d70c76d5574614d6134a174ff450a8930c8142a1447fea7923a08b13b02045c07c30c893c0204e010f88d3b501600aa9ba554c69d8a5a215859e503220d8887000de8e6032403bc1dc55b4cb5dc34073c7a40dca4d089e2e452684278d43bc2c45149b033a6dc79a6fb2210fe68079984d85b1ce5a5eb1bbcc679ee305ce4ca60390dd3e3aa05b2cfc68ccecd9610382b43fa790216e91b6660de5ce442b4ef760831a0f95ecf403df2603de5607069dc2fa81cbaa4bd4b5096b0a83d3f16faa4e1edea5d9ae6fe468ba7755021b53ee61896c844bbb789a758ec5a07bbab24a82067c5359dd90ac2357df31dec24b11367f0c05cf76663f5137c4f279ff71d3122ef7e640f958d3575b51a843dc8650b93969d95bbe1a627a9de8ff421984588f1348a5252a1b057a5e360abeec445535fc855a381d4b84732e208f3aa3e141e20c48193f9b814fe2941d10792a2d00b2c4a539b721df1bce830d098c03301a45b76734b658d4082bd3216fe16375d459a0e7f8e9b50645833658f956513b3b5a38025e3bd84b302a4d1db916b991c075e8bf32befdbf53f34294833159f301abc58498d4ca1a20caee5872a71193c3c37878429bef1063b8772f9ea84a10558474b1cd5227d448889694dfdc19c42e0769e89551e85e0ec277ea2ad4066127a7fb68d7e467298dba828e24c0548a1e26691f8edc5b46082109dafd49c6c6ad1a18d531549eed449c8a42bc31f7df13ad91ce8ef5fe3ca821e0e744a78fc14ef9a9b9042608fa3a4b2fdc4b2bc84f0bc755841b97d1874be20965b23418ef4acf8ac60599717dd25bb1cea870f621ee8f76c701a6b876b4a02548b21e718b779a969b281846563bce0b00cc63c0960db770c50ef2fbd255877f4baa514b4876c52105d27ad42822aa972b007790f048d72b11e0d67b224aac6a14f4521777c348121ac6ba1b5de2b3319f47ddcd65e0b450d63fe79200bacaa597439943fc0f02aa15473504a5d05b537b4199d00d3bac01b58c8948dfe56a499f6c9a99f96b1b160d973d6b1b1b130ce97633539096e0be47c0c5ab2889c5dca63d3a06bb3b9a14927e730b06605a3b765f49b7ac475e35e7c2ec409e52a6b9f5183bd4a0149d05098cbd9b4843324aa0ab770c623968fbcd01e313ea74ee49f29c06db2eafd48f4b9063e3e066a98ef4c9c37e555b465571171103ea3ab8307c911dd45746fbbf805750eaa7719e84916254ddbc20ed3c2ef591dd83f02f09c547f70217211282bbfc707f140d548905c429458e3a34dfd5c24b3f3c772e0fb64221b787fa4dcc812454d263f0708f4a493edbe1ed84083ea079f56413146b0729e190774ca56e9bddba3e762253c365d2ea2055868f1a9727c6ded3d7961a149ab2b20540fa6e658dd639610f46c691593059ae4d8735ecf45ac6d062d6dcd77fe2a8dea7d1ee53f967aa1a83812a9b4707551c49111766695845f9730d5fa314db87e98101a8578716eb0039c48abbbeb3c17cb595db16c114c56decf790f09642291258aeff494015a18fbd778d902c1f088633ad060d5fb870b1610164f0287060d764f23912b9042a4a2800785b604b1c238c05c8896d05592bd005c040441750ec8e0b35bc014e965751d33de00048a6197a4eb1a51d5bc4048ebd05e362a3db4396dd74b9f1f2b68cd32c91af253babf14f8053884451fed0e548cca5bf8f5242b1966df0ade697090239f8aef8c03173c27fdac08213e6bec0b0f7e9fe02c8886cab825768ca49fe1429973e56b666c07b238e5ab43e02a02dd431ed21e0f99e2ff68b7097748c0be289ee4a95a2c878ef4897a1bf3e75065c814ce71997db8e67ce7c3830b41bace56cca1600f41a26c2b04984ba5928bd08c62e63923481246218eb22fa568319a40c28790f623488affdadbd3314a2528fcd4107b2126ac3a714b02b0bdd952a4c1173cd92f6ebe25429040fd6a297d5357d42cdc0722483e41276146fbb8a00f994fac622cec974420de3744921f261de9ff38181a75461863127a5f4e4706de1ac6abc60d119a829e354ebf9ab980aca1dd88e58851651a0155b473d0d9b071a06c20b2e3e552ded146f0834e3aa951c776331b5a9811d0e8f8e7e16d83eef0479144aba9cfa058e38c6e6babb928174eaac3e28bb8551734d9e5ab04666c160cbca1cea14f232450138e94c69c8b2937a444497d326c1f6efc7b7a168a480f481387f293cad277540825056fd874eab1859ef33e4a147d6b106b5e061fe226b4ea9f705181aaa064e1830d0917fa953f4fad25158abdc165552e852686e624fd16886520137a7dfe53518405ae60b1815df12cc59cb82d6ee8f817ba392236a68a4cd1183bc6cb89964be4738e7ba539c389f1541df907b3c42290be8a149f5e01a18ec892b2efcdab83699d9f017d2aa1f92ac10e6177daa2aadca2ee61f136a654f00be921617beb2d476bd2473d57c834dfa9088de77deb99925e378db6569ff84827ba890eadb4641a5c4265e6aad86c652a22d516c66c4ab8722a5eed5333340c63d6eeec2baa92b1b9a296ff44ef8af80b8e93250b44f923912d266981343123bd50712e611cf490a150581e0ad8e225cc6aad068f9fa81cb0ec2efc00d787ee49cbdeae0c3e34ea2a147246a293ee10399c50e8f204133b9afbce1fd9c30127535ea31e8a5f91fea91fa87152e22d60384c24d6146da870fa90938ab46c88a8e53bce5ff99b3cb166c734ab956c2b95c5a11660deda83846bac956cf0543f7c275db01ad7baf799148f95d4f525949b868a5153e360de22364067e1a19e9ea12725f54d508b652210c8ba3980999af77e08ec0262c3833474c2406e36b44b5a12c0e9d3d8d504ca294638b2e77f4cb0b860ee5b87ee5b49874d1b5223711d0634180c5fe41ae1835dbc072f600ab25e684c5b140786429770d2413f7fef12a25e97d07d451afa2412f7fccd668b0e3027765608757d079b172cf896c02f1ed646d8b7497a8d2b822c0d09a23bc1d60119c1446a1690859d931577a866d600b5e8c2714108d4f090c376ea23fb69b5d1cfde2535d6a8b4c8dad9410e29580afc256c3540fdcc94367b62c2c75a818ebe0c208ed99e07fbc246d5b819147acc6201091b1058506f5156c5b74a535f4db47484da00de45ed81422b7cb3c342934973350ebc9eee78bb1e9ed5df5f8eb20b6a3a89ca0f9b41b547eb247aab8ebc7125195ed5a2b75be65edfe0ec719d1c4f557137ae25edac6ef4d0ae37410f4bc9b9558adf7035696751229be5965bd57837d726eb2ccab20f6e71551779db72f2f60d74a35f958893ad6eade4174a659b4c2a545e8a20cd0acd67a174580f72a1a3d22d9bbcf556511d99832060afbba9eead92a94b8d9240723c0523a1aaeae932a97e15116e2d4aee5635e2adabd9f31b0ebd5924cfaf52f4d6b5c95dab47b898ea25ea7d29496f55a3375d27e15009c9b66fe65631f6d6d5a41d3582eca3db4e55636fbb4ebddf00dc7155157f32d64fabbaa5b26c10328148d5af07d5426e541f3b5e84ab196417be436a8a1b179a0662a0bfb3b1b6ada509169aba380d4684be8fc94ee551d459c5e591fdfc010d9bb93bdc3a719d7a90d39832d75d00c2ad8e8eb5cdc488bdaeeba4cea06e6cebdaac1fcb746293e562e0bdba0cf0a629ed89b96eeafaeae6a46ec3ea2b9e5e69ef0645bf8eb94b91e4262ab2d81ffde5896f9dafee60d441a3d70ffaa5ecb417b5e7bea2142d1b74679a335de2e63a49d6a973879edfc8bf267b4c6f85a5d1b37fe411283ae1e61739824d1d3cdbcacfb0ea4c7da98e28195568c2fd8e552cfd75275f5cd5814527db4dd38976384b122bbce9975b9ee96d2b0b1af49e1ca4e1e58a9f912b0ec6e4779c4a11ed7711e441a5e23b6c9b732e0b3efab219d2c33765caa7a4e734b05059ca36a4a2f9904e5c77bf8204aa22f75e014810d109a515694bb07115c0a53c78ba0d459ccf359d0a46a7f5082133ca13bc41f9191094af5553378b05a55d2519d02cf41398171b501ee21466028b5243e7c5f103f0406eae48bab233ccdbcbca5d8d345612091b0243d962f6995fe6e389dd45cdca3984b45baed03ca8f7aefeb51c7ab553847d6cb14189f012a7eade9375b3c4743f9d67b89f764409f1b58140b36aec80dd3255fad705170766c42344d311aae17da3dd23601700990a04af0cbd394f5a440ddd5712bd2914d26797f38b5d5d498d8beb64f92d2d5aa00b01463611fde39a013b27d8e72b84631031c01f9390693cab5145895c6081072929e321243d9bd668f99c1fdc23ee294c3b03b13f548b9b56853f8bce5eee2927febe0d7690e93d348a2f0fb1fc107437fe45dcc23c82969487783c914427deb5275d3043b54ffac0572babe7c26c154fb1f2c9157af25db93df941f977e88ebeb5f69468df9a9680138d3f3bea0b931b9a709226247060e1137b19cccab433027c6b0a4b06b78934c7b5da1dbac9216a4392fe6e88a16ecd2adb4bf85ba553a52efdddf7d17f25c5e39a20ccf5e15b41d205da9952c4c069f85f4f235d2675ad5b20439b29e23aaaa93f60f2f2474581f6c712da4f5951e7aa25721ab6022074ad302a2e2569166684ea2812c9e035d14ccdd48be527daffa34d6b8c655f6ce2aea03882bc5868a9101b43aee9d2ff3516d0dcb5e8da246eb4bf83667f167bc505e105ff3da91e2666c434f661d97542a37506b3bd78eb3d0eb99e5aca0de2a1f7515616329d3f0b5aafb2f235ceca88eb8c69605de24279d814d0e1bf1f65631181d99b5115894d5574d6e1aba71aedb1ac42f63e9573dbcaca48f3f6958166c4b84bce5e0de99e6079f1e6444dc8aa0f60ebe8cd4432009aac9a548f16c86647605e8a584e41ed8cf5f035779b9b2e7d418260672a9931ea8c8680d99c51bad6f53c86eebf204b8692c5b7c2b703a5d690baa41b247f76a25e4f3dbea1cf1c39758def4cb43a3bdccfd9271b2f7202381ca7744328ab80ad34abe03aabaeffb2ded3646402130a9eb564cc60d28084b779ae10173c8d4432714828846c121e1571bb92a372209294804213db70f4517f06838a869c9cf424c410aadbada5fd46c880340ed3e260486540d5522b26ab5e590958ee47c9c748c949c173a917adfa75d6c45291175c08d711d270498bdbe334877a96e61fe18d5519e5ecad4cfe28dc7719c46c14fd44ed9621aefa5b880e4dc3da8c2c824f6e826ff6443c4730e270d86133067d70499e7e0ddb56b01a40a8b8125b023046c22402db527c007abc9f99b49e28b3f8cd530676bb7cd6dd2e8f96a4670ad7f6ace161cb161663a29e94f747fa1f75b3c9c3618c5dffdc8a56d6382c21493708e4e5707efbc37a3bd468353b051af851f45f26aff16368abb5ae0c5227e1657e8ba18df8a04bd7cc84863d58191f307ae39e74d59e36db04e6d70b6481fc1c98fdcc25a32aa9fb698246a4b1db809b2016b0c37c29674fdce70ac241d8127d9917ea45ddebc5b46d1a1f1414ff46151b4a29e2fe7ffa2c96c36f9c4419372353b71b2e85bbfd9bc26e2aaa72b7c255d10d261f3046c78dc0c0a85a8048b36adeb874edd864cd843b92bd6dd0d9a808f785da9c174693843dc435d0041b693f7ccc9aacf24b6f88d6d20cc997647fefb305dfd8acbccf86f89df9b2879b4c0c2aec8bae18ecfb516443d01ccc1e8e5137464b2c7c4d0511afb580d8ce026dd0fb479a372cfb5042a21e29193c30afe4767c37b7c587fb382c3f318fff581067c17f7dd75528541fc3b4241b6eb380c90cc1c605eaeea3bfc2ab4e316a9e14b6ba07b9ee997decea12dbb80f4dfb1b1736b003c568c068801fe48b51476af0d1982e3d4b5f2c7fbcafa79774096104acb0e36ea2e4a530f8bddad31547a40780e30183ecf57bcf7a7e5e43a35423298061d328a585a94dd0d93b320a9e3443a99aed49aa660447d5ec5c849e82e113e3c5db1569216008594285e21e55c9dbf9fb6f5c8c879768806deb1744b7788a96ba1d1f4862845b9c2f75356f2897585784b87074c0dc95714e96383a5095c69be0f641f3026d7a2ffc82fd5c9f847b020e3b6708c1fe643cc9949a46a0b258370d481a0febd691de253e90e42171cae5a29f0e0748ca96fd4c8e1bb3035f2974664305ceb87053aef186d48a6fb0719b5afd5565dd44d58b1a6aac03831e375845e612f2435ac773f5591631036c47af440e8e4c65cfdcdfe338a140fc03173ce2f46e25e8cd8cec1ee071cac9d40ca99593306d2938b970c0e08af05b3f85e76c85c240723162bbad7de80640ce17257409164d9346cd9ac2e470136b1a0af6b7bea4e36d11409635e95f6409d9a45715933139d9f6a886c1f3eebfd91406f47bcfbf96845eb5307c441bda3965ef88ad94ac9c526934ffd8dcdfe56cc2603ee8dbd6b5f8c989bbf62ba8e6c2af3d5986624d2e1ca1fe3f048a38b7704c26d9780edabae20e649d2f90169eb04b68539c318e86eb9e0621c5e279f99f412151edbdb564eccf1b1c22af8d30d80d6d447c0238cc404c23c624d112da46acdc8b8216caec5a4f501d92c61f542e8ad291fe9a01f960aad20c91b263051407ca607c4e244dd40645e459a709169e0d97fbb29ace1dabe5901f5a5c777728014f44372b0dc7992ce3bcfc597555e9d71eeeb5880b207390b00a4b93d6af5e55edc905d8b4ef730b53153de33d4390a60d019065d5db4cd0356b47997a60b4d01b955ce82c84e5b86d45672a131819711b94085c50b1c58fd2c84fc5cc0f350e674a7a8d18f126f874f84036631c6906a4ded7d5ef24fb8336b5725c9bfd68557048539b5b2c40bd79344a97af82184d9705006d96c7e98d1b0314e5eac9db783c96c9d700f5bcf09f29ff2601cf84720271398f49005c8d3436098dec9c0e28ab2a9b5f85dc97c3b540339bcb502b833203114241ca65201845859f943870314c5dba169c148edda79b2e8f84d461083ef63a28ca04af78db9903da2c25b69449ec7d1e9eaa687714380f498cc4077fc4b6987bfd19248b8df16ee9004013f3a49c5d8caf426cea5c8ef7849040bf4cc856d04d38b99766a3182dc05fa90b4eecb24378ce6bad33e6426a403ec34d99b1b2461e11c10bd8b49413d9a309ff756d6ac44a53ec2fa2821305e2f056c0875d6e7bae95018e438e95182a46352724316499a661cf96cf67398934013e5c61af89eb3ffd1837ce8ed594ddf12e4e99c100339aa676f76e18280b79361d3bf062741d2542cb08b563e2ae70d9ab61c66604717f23b8c8fa47b9845d77980e5dcb30d34626f8ef0055f1a24e4e6415c75e5e90b4b80be492df81ece3c385865537e9afa33c2ff7103af3e87471b18272b438c2f3bbebdcb91170ea572993ed1a3c2caa65a2a07c4a9b8dfa37e79a347e08940d3d7b1f253cb6ab7b398ecb4647025617861ac53c05f20ab553ffd9a66229114aa584a78a94ff3bdc73559ea4f02d0ad2584f5d60798c9e3bfff1009492c3824ca4b20ef753cd9e787cba9c087059508428fba152ea813a92c5fe2c7901f011ce7139349c97c88866fb4b2246c6a0ad9208811b5f38a35959264204096357487485df481d7a91d9e3e889260bed7a241a1e2a2c08df44177e7edf67aeff1627afb95dab6f5740012d3584ec7a8f5ec6090920bdb06b6acd1cf82cc577892ce845f3488a2791bb7233510ac681d13a277beef896d966282f82d8a40d3988186390405ad2c55568bf36db9eb5458537d3f4705ff7f7ddc2f24770c50e29e760fd2ecb9ff474e1a056185db0737411cf1230d7e2deab4cd5ad826b38e76039a750e1061d4f00cc281405786556a73903025315c2071a73403592e2475d3461710c9ff725c7e4b206f5a3ceb329bacf4f198432737c223e298d41e96b46ba54cee99398eab4d400e31c783728400111f0e97c24994aea0fbaa147bf443052e0974a804f18f78e00e7c8343fc05cd2751878f4b8e54f40f9115008763d389cc46fada37425f39324e6224edf161218d514eacf14d3824ec0d684eb21c1ac1c0560b1e422b9e7cb90beadfdfd43bcf2cc77f3c9461395301df9a0007d569878711c6f1de0f1766b7bc12e7a44c5d5bb1b3069ec64ed3154253ad3de9be288d6e130ae13bfe62c8346098e7f90ae053dc0d5cc08a2edfe61855b82e32368f740d92cba04762f9be936f0e2469463534cb9ada90d45472bc30985473d385ac8930918b4884c6e48acf01b41d0d2754047bbee9664dbc07a18498616a0c22796ef62a8b4351a2eab7b5b3ef9fdce028e7d1db5a1b085cc76d5a6a43729a8ccf2d6d901b085f6b41a2874225b19f535fb45a41b96dd72e31d916af339a611995c4e0bfaa9015c7ae0ea8498a1e4437d7937c1d61b498cf598217a72ed0bc5698149f5e0c5a53526bdd8332901decf8f038536729bb16c895f8bac87a58b732e059027f3320a200f23330ff1d56d7031dce7851e98e345b99afd043394c59ea18a03868ccb45f25cf0e9eca4d14000a7c58ea9c3ac5eb84b1963a22dbe916a3f13b1b3fc48123db596746395528f1ac9a6dbb82293c42d4b56be57edefb95652501efdb725c3ec0549eba3483f5a28828f634c91e8cd56b6d506569de1c7d0f47ca8b5135656717a73d621299c993fdedc60f02a15d68a2a8aa6583c3946a54e55a9852328078f357d43d0cfe1ec9009b959b6604f4cf8303cec9e423e45e21c820ffa65eb0fa0f7b576cb77be938f52a16e3cb63993affe71123bd0ad5621d28265fb02898bdc5d61b975ba9cb64d5df2c9dd1198f3a25efd706359f265b7d209a6830366225eb3e347821eb26a608750023d756bb47752eb855d89c25e85b21a425f5fea14957d773d0c4a274bdf9efc20d6285d2699bd9cadac758ebe8bc4564717db22ddf7343b31eefeb57843ae8f624313de8ffcb76db365aa63035aa0ad341d340376cc6c5df3c7f14c36eba91728d5b45e84bf618bcb2cf9cf2b29bd995c96b270e2afb2df524f2feec6a4854b1cd6f9b929f5ce54e6ab4676fa598285001d5d34054fc511eb5c28dc69fa660682d5b5d6416d2f4b92c2d77772939a9bad90044a26fe1d765c03f2149a1869ba5260072ec13617172a170d9175dc6aed5d71929639540ec3024571fa5ed4d1a348b1b571e58594e5f4dba8ba4610abe164578fc120c98225b80be736dc57f0ae2e893db3a0bfdb111a4c03f141e2930cf6f5ce8840be17bdd45e0224c0b5c7af8dcdfefa5db1cad12748a26c1fe6c69fe6989a3af82681a0de31305b02990b01bdaabce676f21ac8630c11fb466a47802a28156cd3d38b3e490e5a6c20be86387afc0b961b0d117828788482c47e971260ef60dea09d35300a0336295767b8d8619d48908f20a73532497d13297c1bf0b9ab6950868428a9aa9e5a40cc672f384302777e3f226bae0b39be0db81ff4c3b99b63bebd4b990db853210301a83ed956827b9b5db3a9986dc79fec3860585b619b7f2107d713ca82aa1dd071a0f08e805c05168ee75c88646219d0c75a541bf0cfd4808e2c7d7c62b50bbd2ee6e171e53402c464badd70041a7640caed574b86dbb0e752306cb07fb9ef55b473fa72b903469194cd46047888009533282e8afc7a36ff48247139a9639f8269670b96ef758cab3410b8f2dd423e2bd91f28d47a80bac66d9c571e9be5b0d27519d080dfcb690eae41fde17a48a94cb121aff95b79343a7000a460b9bc0ad96a5c9380a5b6e8daa181c77a92328820611ec0937124c550b6aed9d558d9148f497abc5633c4529e1352809d2ac9510a680eb8da8d07c4c11b654aa25569b51dd0e2366eeb9009e92d20bd8e66be8d702c1e075c46f4935727e32be955bfb7f0482fb33ff083ea99e026ea6611945fb55ffb9ffa18c3cfc6290bc13524109957048c2d0d0219ef748fb1417b6a6cb0afda3f3834745a53ca2946e11664db36b3ac381197fcac4b6494300e4887d065de3ae4e5e3f0ab272f30e98abf854141ebed4d4048919364cac315c284e45053e1434f82b5617ff3f5cc445a20539c02b8cbb2d06126b1ab75e071d41f03430578305aa54261704915dce14a30bcffa4eb5a717ae478cb005e452d5045b0e968e5eaddb56ef77f01e61a00527ed70165cfa04e8eff042127de058b54559d3a0502d178ea17eadfe157df8f0994a052314f9eb991196137ef0a2a91821688887ece2201bc1a0cb7c94d097123781f3d7a5bd175dbeffee2f4daa4b0e62f44145cfe9d9fe64f4b540f1fa19713404dfdcd266f1490fcdf0327e64736365930144751737801edf11ccb476c6241345e3e1f160e7beb6aadd19874e99c72a71fd3b4f1aa3125096d3c85c8d9860a2e944602c0582c3475b505cd63d2083c80c5c4b41254b165fc02195e858191c0055a60eb34cf04e95e7ee70a255c82e28093d6b3aaf92d4b62f597c0087da485b1d873b77abab3b2d55d056da414145e57706bacf72cbc465079ae9f3fa05a508eaac305f49fcbe0fb594cb2fd9b275a3c472d5e98d520a45e68ff160689116ffd4ac4a99953b94fdf1c15bc550afa7ae1783d8c4de6b6af2fda85ad4b9c0fa8ee4f313256eee5e9003a803b2787176b6fa6d155cec3ede2dbaa9f13023035b6dbcf46393a8ff5e7536ed7714d10f7a52596eaabe9445aec12e051be07237a937ea9360cd8b1340162f863b52665b29ddc17853f8ad3a8dd6f7083a4f4621abbcb1a04d8fd7e1d8e11f07c8ef09362ab45463a80d379c12f240f8d9acfef06e9fe9fa32239b215a58e9606fe496e26a086407fc8494e57099a4b582872f17b57e42574f1959b3e4e48a785f7063313f4e2d43e27081f0205ccce3a56194305c2cfa5a4184b5c96ffcfb886166ee92e25fd169d1f057066bde1649080eb8d0f86bd911a9781f1d6c51a83e217d5dd1bba566185001d6d8073277f2865f7f6b49ea76ab3b65ea887ea476843fdcf95e4722f1da4d25283a039c5ff475ab88b6b44d3836dd3c6d27af160cc12b2971950ab404d1d285edde6f749ecb3408ec12cc1aa5db89d9f20fff98e33572b73c29c435216271195ba075f64dfda888bac5593e9b51430e9ee1ce84c2c4c412d87fbecbb7c0939c9bdd330a4650e2b8adc5391d5a77a6aaba4f7753ebfa282b05c37001c38c33a1432a15ea9739421f7aa39636aba9d5301fce1b8d3fd290ccb829b504b6fc8bcda48f999a56a82a1fa064c7c24e2cfc2fc820aa5e14bf45ae46d267eb6a92c734ae126245cdd7f96a4509c9261a535a35c3038757f0ad10554d20c82bad27f432602035df6e4772709b0f1d0234998ebd6d3021e10ff9e6fba0ca2b852f5d7700393e2710c2854dc482e54c44b627cbd0f83c83253d29c01d768413c8978bbc049dd802946869ad3773d5801729f869f2eeefb551eebee26c6ff31b65473e4a21c6e26a7873711d3170e86f287f92b38d90158d11b0f7461af7f0567ea28ec87bb093eca40b8e26118762f0039e01ee36fe26e147f6376bd7add132e6a694145c2750457b774257a79955dfb00ca672141f140ed9a1ef0e9ee48a74a219d5b1f3c3b46c67287326789ba0f533c22670958f03572da89d39ca0dc44d9096953486421c82edcf3cf365972bc966ef40cf1cfaedea94855803fd4682b4d6c758aaa039240ab39e49cec4a9fd5ff4b64fd0d94f0b38a8f53abac9d6f54e444f33dd191e88a63a01fc1c4e316a8816f81fa94e6751e4dd106c4bb6f72129b9f775fa38a744a7cc61311c2ba59ffba8897c22e1c838d0300c0df80310268883ea6c36e875191507afe71cc14f7d9c1a4d1244e3f0176b3d4af2b6d3c4e1b7332f95615bc7dce29b7aee5acf9fffcf1fa0d364ed5c8401963cc7e2c3d03f8d533e82527cc7dd0c5f92cd46cd9c2dc514010e0129c1be5b8a608800ef8ecd31237292b86179bf6f7931fb21a5a55ad8fef039651dfe21d7e780d363bfcbcb9a365f9d375ffd577713d4380281a7caf1b8939006cda5fa607324bc08f567a199396bf427ca366cae4a281e4dba79b5974a47b9a7122c1484a15a521ac4895bef246f3bf6abe6ba471614780d9071a7886c5396c77b4227510c9e99c68a2a08748e38151990c3c2ccfe7fbf0f3da6777d5aa87aff0446693ce68235f7978cdf9fed07080d894ccf0c968d92f481cb6740e4ae8bb2613aa2d00454f26f5624c11637c6b216e31c54da173135abfc05deae09a4e4732025b687343e289096879f88a030aecba882788c551d25514ea3c1cad46472b9df5faec8560ccf90cd2c7401bcef5756e46ea1fa231b2854f1dccb5aaac8ee027b4640ec10b53f3599494b2a9eb95cc483e0267d59bb5faa5202cf78ff9e3876a3b3d42b78ca0ed7a4c4aecfc0fe6451db9a193bd7be6fa209b0fa27e7afd5497e639c8780bbaa55a4598846602280e4bde6c351dd3871babedc17438d0e8589b204b24987c8bbb3c83c35a09fb9919d45cf0d513c8316677294ccc47f076d0f29186f6527c527e70b249548a563592f2d650a10c06fe78b51637fb2bf549cf486acee4f3b7ed745024c50fd5e6cfa9b5199862d24443daa09b752690cbbd1a1c38d72e48e953c771d3617af0b8066e60ef40961b84af3498cd8f5065b7b2d3bf1f3c5de849f3f56d10266dba8fe34c8f811b3fcd71e51eb5b2e0e9ec376e4c6b7a3995233cfda67f072d69fbf46a24628f6c340950d2a60cb53e3d89665223c9f40cc525ac00f58472a5672f4132ea91445650cb197b4691e5d02c67e5c06c491fcbba4fbdaa2c811052236f57a71d0d4aead06107a382e63d14236345ec4c2b344a9603a6a53c645657d300d14a491ae18ac62cc94a3a30883b621f002b76f4160f894f03882c27f7ea86ef52093d755925a1bb1930e2a44197b527019379c263523a01565f42b38e11445ac9841e9c0b953486dcf0cc89ce023e318db9c6f72cb9cf8034d1cb2590e58a6343a44e1911dd4f190596c581972d31c09abafc46b52e3400593c5cfffe14dc3f3915aeab75ea9ba8a31efb80566cc0af274845aec5d493f17ddd7abeb887aa0b0f8743bdb1c4202ed2ae23e72291cc379e1c4374c17cdb87f2c39360be04a1ce7ee08948207fecde49c076fcf37751489d883666dc604e3e3f772250c13fb4ed2467dd7ff2d949be49869488bf9d36489b726c609b72e0a9dc3dd03a9191dac27a539db0f3edadfa14489a97fae2907482febe6f302490b36c7a1c21de1b974c2b164e40718e4eec9a8de34cc905bd8ab5c3848c2b27b5ac4c4e56ac1eaea47261312a2dcb0a8830ed12c3a5a181a9a97c2016bb4e7a93d5d726071b4b8754a9b980109396d54d44d856c9e9dad2c194541e1c455d77afc9ea34938185a1c2ead44c58885d375193a07e4736d278cbc25c9e911cf38cadfb337da83993e11fbfe49b4de2d7bc9726866cb2aa6f88c226d8109b84f34cd2db5585be26e60b0d89aa64693790185554c811743eebd384edca2cc726a182dd2e2236c626fea0f63fb9eb2a2e6a9a33554f0df26a78191db3461d100f02aaa172e4b8eef358aca13314ff69d5d0c7d3f565d4b3e5c5fef2a0bc5100aa904e7e61097419b1213f197a7f2912b3b5a5d2701df26b49b99aa1a654e4b0982712b9ce7c0c3f3d43dbee407a68fe65b87af9edee5006447415386a2278f15aa16abd3a437ee700e62845b661b0bd4d669233e85a8239b001bbed9a161d687da141b6528757d6fe38ed038b1433358ef82fe13d8e8bb2b22ad4b37cce09f243e9efeda6f0eb0bf3e53ba057bc5c5ea65f8002b0c94a14018f9837cadfa98ea10df2a75609d8a022cad00ae335cfa055421a1119ca5a445abd29ada1dd433a42b941ce6246ef0fb5839961082307048034c6100849cb109bf1fa4e13ef7703a019d2ac8cd66a7f81e9709e9b95140aedbba9528564a6ba52ac7934b242e532f9f5a9a673695ed4e85f3863cb8767173622a7ca03874fc65372ece96212420292a6be4361c753976381078273d75dc41224f380525592820c4a932ba4659fe19d9ea3ebe186d473c33f802f31dbdda35d2f0d22fc2bfa1990cbe97d07a5453b20a55bc0f90e04324645201d574aaf71973fc363c89a4a38b7004bc2faf117d2a24069aa90c9b1dc1b3111a9262abe89c25bfba7adbd161493aa3fb6263c6586b2280bd2816fe4b84e30f8f58e8f784b3f6ab59b36b6df135b12e98be2e030bd160080446231d4c38bba6ed09dc2c0f6e9a4d9c798bccae1498df59689455a64c66d073e6c43f2be80042977c5a2e4395600a5e82fc1047a8da7e7a9bf7c89882ef6e7db70ecf7d56eb2f0020ecbbfab1ed5e217adfdb3847099436b14fb791f07de3b2d1b31984e874d65c2145d1dd842ebf53d1d4bb73af49bafdb32262ecea02bd4424d6453c34a86dbf2fc4a703ea2cd454aa699940857be2f7d14953f3b936982cea5d22073a27a82df7df2c92ef057bd1d5eb0c07f889f5032b49f013ad1da171a83be0539423298b3717cbb8eafd659f842ce94c44efd73f0ecd8718dd4c6388af099891cbe70953949862a81b539c9525699f5f386824e701b920888b651d59031b1c60a13c7bcdd2cc09eb7d304e665474079e48d35b7d9ff9d407de85ed5568624d6bbbd1389358125968606fd235111bd78a87a988bb6e5a6089402ade5e9f1cfc580e021397d981cb0f011e7462c317e222a6fb144f4fe2f5139ee67a64a57de9c2651991c91a98c5e1395fefbe946a367cfcf894a3cd10e106a95bfeca26a0c73772393d501b0e80d6cb0ac9f79f3b4254ccd2543ccf4958fb6554d23c529abe29a6a13ec37085dcf477df231f405a57c591b7e36fdb3274b87e364cefec3cc6e99225777ae302ec994085cb9efd778b3efc692a812c46521dabce3617198e5a5f8476d1672839e5fac8fbc22794956f9d3807d884fcff9980b71a708ace3d61cfaea8e85e17a81291026caad070bad2eec2f589542ed2b4cf9a373dafe6037bccffc1c7c85bcd762c45a5983e079c0825fdeadb541163d28d58f2a81284dfe9471cfefb93cca978b95351c2f072ebece9ff337d4bb9aff2313e39595fa900fdc947540bfcb43746a0978ad23f3c0efc2f49ae6a81c847ed5f089d26421814c84d2b21b03b8fe8014fecc0f624fd51f525b75cb465ef93694a037bc06765882824656adfa58a8e0b836a0ffad35fe65115c6196c894cb8e38f0201ebd82bdb8192064225ed3432d5c4263faeccd68374322f6e525b39672e6174556f6a0a952bf6030594282bbcd09dfc1b45cf8c8f9c1883a43cc88c048ebeaf46b737833605ab5137c3682696705207e04eb0fa77c2f52e87463733ad6c33dc319ad6f69eddc91b09e0d040d2db39ade823a29d18e460d4851307faa27e6c03ab74dcbb317686bb65397902684d347919760c8cc1a982652e6bcd92b373d03dfe06a78f9e55ffd14f20fa52994b09b13a6f62bc872e064d4626f848b69f9a9e8952a9f26efaa0a4a6edad762e4bcbdc6a28b608d70e05b16864e00030b4538cab9632d1fd0b4f44257728c330ad8e84c43eca4f6d4371add9a13ab1fb5589594077b13460a920e2e493d15952b78bca96f66daf9d53dd0681aacd428d51377a25937eef17e7b8d2406ce88c77ef082d1227e1166f2a43c1cec040a2c79bc69649feb390e155cc7e3b16cd9d54d8a635eed63ab4b5b47d1424afa6708faa9bc40cd12558840f12613226cb3438c76162b44557051360a51cfbd01d4f5cf76a79f2d1107d05ada6f57cd11a4a04632870435abffa5664b2fc96667038db777e2e3ed15b4c684d3f662541ad776b92b13f021b287703f0f0ca6729239089723a88b19052771df657db5b5dd44f3931fe0010efb3877526380db9a606ecc8df19f7c224123b81dbc0a2d9cc068198dea4c500e4aad40732edc50c3f338a3fe183d1516081ad0f8894868edd8ea79861cbc1c96501a82926a891227e36a7a54fdd4318337345c7275552f9735a8e22732e2ec8abd688306e6c167060c1bc99d59238a4a57fa459040c37cea4a425705611e58ca246ee1b4124f2f40f240f4bea216708278c5f92bf1e57d9e9b49b01806ca8ce3e59891fea0225e77fb7d349eadab53cedc3be5a9ae6223a28309d76e1d27fce5743b679f8a00ab62d2c4dfb91a8bd184f2756900adfdb2afda195582c2772f32368ffeab16945c3076465900ec82ba7d9e294e1d55fd5ea0abf5175b478ab2b981138d12565f911435807ed223bb35c27d24a8b07e5ffafd92a69c5e8c10083f2d5cd91faf37f48d244cf3b2845136547ae3665bb8ecd3dfb21b033645d4b08395c4fe35be20772ca6d2fb671cdd3cb2b9c7bcb59e825dec2b604b13520fb0c27be4b687de4428e3620672aa27ad96737777d58df347ab90a80207c656a9b139dbfe1d5b7d887b40739baa07fde4a79b0faac11f43ca6331d10295cd135198c13e0f321d7711ad8cc314e5df2a3ad71380974ba298a19eee4bba32705d4bb7b6497615279beb73118458b47f8377332adbd6dc56bef9d6641a19df4aa5126a303d1b5762552e9628978b9be7e0850f688cfe5bcb56e32dd7ff8cb31537f78c4312c5bccbc2964495d8ffc680ca4ffcf4449f0232b26833a2fcfcbff1bd812e67eec3a6af41e9ae928ee7631b35f7a3dabb89826ffab88dd8266c589b96e90aab73340a1ef8cde0ed91687e89ed94a09410c270e37844580d4cb422665e484bae907c9b597082de1da808f61097f9fd6a4d0a8fc1a7fde3cf888098913175d709ac8e747fcb6e70dc1ac1424fcb0b1699a5ac019b017e8a3a8f04407d47b395a1bc9786a50ea4381b7f12261996f61071d59c09557b3405fb0812008db6b99c67fa0167702aa350d2d01335b1c39bd875f1f3595d41fd5a30368730cb035c71ffeff893602ae54d6e1265c7a259bfaf617480d216caf7555ae89a26121da226473d862b44853d0cbdcee12d788679145aa1484889e747fe5d38f6f3af2d0836605389be3d7df41667937cca1e09a720a66459a8b93de29c93971e428741c3cedccf774978a4389eecbac7931eb9ab488899c049f83370a8190569da2405af27b55f83549ec04290d639d224e4a0832d9d393435480b0a05ff27f009ff30c26c27169c5c757edfca4f0cf31dfdc11cd1b853464aac4a0c3cb308a9f33d8369989b2f432b273346840e70ed2181dc219b3ddf6dfd2fc9847b5894809f30497c06ac8caf998d35118ac74b00337a96e8ba0255e2e7e870eeceaf8b5fbf1134bbd85dbaf7d7444b47645747ea2f2323da34bf56263be0fb81b903c6fae48b571ca1084213c57a604040c906d844def77dcc6d283027796a5167f29134ca74dc21eb2dcca56aadf308a6d4b05c955845d8cf2daa92cec1b666ec4ecb1a474fc9a6568c8d43429a7cda22aac68fbe7c64a38784cd2aa88527a5ff209e4e0d445edbfca51cc8901821447150469218e1f7c3f066ed5528909985d5da49cc4bcac4d737f24b328226929bd1f9ebec52142633628b110862c750b639f70cb90167b54614abe0e03585b1b802820793de4d6593083ea57793790bbbd87987223261a73c251e7cc66b25bc9326aa2a1b0bf4fa3e81f567c2dc4a0c6731b4828ae5b5371f9c51310cde20065842b01d2aa577ab775644220796e52436c31bca17b7058c0b4751261adadf7a42705edd731f2607db23e131a48eed93f0d651ea7bc376d61b71b4625d79584e8fdb5af2d43b91ab4871b75df55105d24ef16744bfd78247a81a5890668bb4a8ebed0cde3599489f6944dde0885b521a93669f4deb13a62b0a0720d45cb3b0b96f077383752ce464f8f56a38e0ca25c5029f09d3bdb6bd06d18c6c1b6e88a7e8b843ae794da458fd086e8ab02db6a99427cab0580b6640ee87a2fff6ace44cc59f5d4a9712daf327409f6fff82d13b35b71d917a150edfe60d9b2b3a4270644f6e012394a90454182d92e4a4d7d1a6fa7e8b1da39494cca7970270770d95033c51df4fa87a3bcc96ef0dd5067f447a0a5a74b4c98a90fd8a4b87543509926d4ab704ff87ca8571971b1d7735c0e8c0cbda208f7189a14900a8cd66da1a2326da24218154de3343c874fb9e540c8cf576775966d573c3603e94570c74bb6f9979e855448cfa3eed96ff280cc8af22d8d27590374176cd1a07c234addaa3814913bea05530fe35f733fdfec3760e3c6f965dbe71d7baaa0b018ed5b5b73b91afdba15f7561c40fd5519eddc13559ed0d0dbef35921f68dac1d2bfc5a2e1ac718435b8d9e759980f40552b1a97462b1b2862fc972bded94cf679da3aad7c133142f310c5b69b3a04c0e18851300c181adb3b7de384aa59aa8383a92d51b469117162c85e0ac9b5aaacf248e4c3446378813e34a829cc49923e5f51a60d8df94af45a7f2cc140d8d00a00bf80d89adc9ba5436328df758ed945a73796245def250c38a87575a5402efcf28050b35e23c5752af2965c5095fc38ec074e8962983337cb630e6891307355c5ad6c88d54127dc135748d6f239c085844753da76f0c040f9bdad62a75dbd4c7776d139775b8550518af8a4d8f2b55872f9e66ba16aeebdcdeac9c448805420118d279fdbfe21878f691d561059b210b8158ac02caed2ef55083a5769ea59a24f15bf19bf7aad14403a082eedb7dd07a411401946315d991978547a313e28d177e5a9b5647e6bc1624c35e7bcb71109e8d7921e4ed576881194605581d0296733143d4207ad709a54ee1b0f3cd15830cbe16896998e7da003a87067b7dc6a4e65d270943fcff3487ce5a5d0b126056538a209e42c80645053a185e288e65b07188b364e7434856211268d1a0e9312f7b9030347283b338cc8ae01249e96ff9afcc6386042bcbda30b60cbb2f33ce4624706b88cf4e5fcdbc2f37d3f18834d8a378066382293003239fa7b8d8ed23c5152c3896df1ded8d047e5db851691430ac586ced7dd0f8f537a2f73338b95d6c8ab52be762a12b6e4c43c1908fc110c4ec3092b33edc0b92f343838f5ed63dd16e61e499c42640cf5c64c2699602f5d6871deff01126da61475fc71fdd89eb71fa486db0d25cd043f410edca3a5cd3a7902846c74b12b2c73158408bc411a739a54fa227fb74007e850006e7028a7971c8e27612dfac00b500ca9650475a2f9386953059b33989bf74945875e5a38ecc43daf59e2285b6bdf606b271cd55a6e436078f8c58b8d563b4e82e14d962bc5f3a795cfa169bf84c11f6b189608f360da988cde942d0cb21ed81b82b25d851a8a9620aa62bd74907656263e1293df62d3fef0c85f4c567f4ee5e41f7b00eb567c63886b796259a7ddc820a9bd1a122c41d28295fea3d018705d662f3d036493dc27e19fa04fdf09594ed0bb85b4b997bbf9440e80f8ccbec901722d46603b5f3082841247656cbc0f67e3d519988e60169ed7a9b4d88709ac18803cc706b2327c64f357aac45d01ee0a725a5bbb4e9c30837694dfa4d2dcac6a29afa78377152f5f567178a7ed7b91b1a4779f669d724ffbed9529b26d46be4bc65ff9ff1dccc838bf35cd9c93bddfb49e822e0146804e68a3f5ba8d47cccae6173e95434a331438374be27cac932f3178b31e063c5c91ee7b87b6cb2bac7ac15a172d510ca7ca87cb18fc51793297c3144908b39ce2c8a26eae0bcd3372fcfac88093eadbb26568bee2d36e46a806d52642c626ba458e6907744464756756a656b445d9f4cf9baa6b2ea3b4de677bab2e923a07990958dbe04ee121d0d737449a02386fa8c84bbd490cf55b4715a8446d71b6daf7e88cb7c84755b18e95bef012eb5f6626885805211ebb585ebca63e91a067889138c8e1eea2a80175db5a05e41558270a4acbf8a68f41f90f4ee9b28c4b96fea3451ee3fc699caf9c7fc10401d879ecf0f030ab522c290a4cf34ac4e82106bc6c566e24266584dae50ee613bd65002bf4e8c79163365d534acda828ad64b6a7d8139072b73b7ab71e04f51ec136d128dd0c75f0917f822fcb64249085f2febca9fde8fdd3d3afd3a222f63d97335153807a84d73851f1c598a48968124228a0851a8b24c869565e0622b69d0ab5664e1d2f09f7353cea017729441435a0de93f3c6d07c1763e84a630f4aa1283cd4771fd384485be1a9abd09431088db9dca183800aced7cdde946dd20650f55623cc3d34f3802ffee8711b9039083c768c3ac7715641727953c7be0fa98e91d9a12705a8eca0aceef250c3a8545b32a1b9a731bc8ec7fcba9dea06d922b2d46461be4f4a66d4c8ecfb6378d86977ef298819c35ff8fa6eb602105597444b00a4a492f4a22a669a67f02ccac07b2cd990a9352f0bb15905faef12d541f990c1ea5853da7e4bb223aca5221748093f9d7fe5ae82618a30232c4342e72fda1a93969ebd10836d40990368f08599ab592e3a1db793ae7119ce178785c19413c1a9bc2ac2d1e64d0d70f2fcef39e7cea00d9c2c440ff0051d5aae1617ceac0838f6ea4ba4c31d00e00798752b31b21dca04c5aba0c6e9a3b2e35a0c9bc47616370fb8c81075898282419f40a241a51a8a00d63b4d513af9d9b80d9b4e0e8f9c9ce26d9aed061b6f5e717b8fc0a250c536c1f099bf558d6ae5d30672d2d5180207131987e0d10e8cf48600fd43d3a5cf4afd1eba399feab2d734e3e8a655347996b593424bbfff162d5e9783fc7528f21d591b1c3027b7b40fbc2c804455f4414041ec922b1bd7832726f17021ca6e6655d02fc6202a67533c20f218f20ab061237f20664f58bc3f385dfda2266a013890a96d029e46503c75ba56f662debb4429d89e1e78feee4c3ac31de50003f2d64e72850eeb91b7a61346efc057140392aefb61f82fbd5bcab1a2526195dfdfe95a345d24fa9624093bd72faa58b9c908de8efec9599d53e890d0b95bb3c48961b817a0c51af0589b743166af5737c564cb1b7af15ea2bcc61cb6107c55e82e89035e9d07bf01445014d91921ff8d1c182a5ff0c0461390c854c700ee9d5f8e9a748cf0ef62acd82fd498d91517c8e6047c89774e9f98736272098cf02fb2d9f41e3f153299cb0d7ffe0ed69c5a2bb019a4f514baf947f5597fbaf82c02f0e7aade262fa0f5fff2eeac980b99f712e2175e3bec1e4fc85ff42d9831d9a13db4dbea55c65d2e15ad7a0bd88cc21f9b81594e3c9551ca6e29d22c01cf02152d100f927373f155150d036ceb0fce1609a49fe56eef4cc0f66987a466dc095fea4ac6a6f8af0646a1b25dfa778b7410a9c6eb6d2306470a1dfd0febd68b79611441729e7b72719df4754a587bb032396536c3d69bdb2684ef4c034bb0c8006c503c9c6f990311dbeabd7d87daf46ad9739778d2ba3ab812fc15da691d0b4f58f22f42d604edf3ce14c8590c491bdbcbc534075649253355b480e9432040712d42372d9a257c265770e624877d945b053829f295ac4c13901bca69ca23eda86cf0056e44e6c8fa96631a9f08f830467ba423125e5008651a9327a1524cf6f8b318d37ab81c21a7ee13b2c0ece2a8ac2f05511e83031ec144090f1aa18373c69054421fa01d96105bc2024997057219190104edbf8ef507cb3b2edcc8bf1475e7996b51e6ff42258c4ceccacf0ec945549e8cca67e775c02512c16a729e75808e2bea04336be7e92ba9c6139028b10dddb1887934b64d2585f365781059e90c0c0a42096c3864b682806a00b2ffad3615321fa2d92807578cc1ab28df3de370c62864f5de4406469fbea1685a37acd4d47571d29c6be2494a6ec30c6deeb64abb41d3ee2b3eb87548bd04f98fbe2d7ee11afb920a8caf4c86b64b449e329eff137a4f162116cc31cb0f588f80b9997871533ce20e7352a27c5dc00ea045bddd84ad50e7b963a4897d06622da5db95013c5107c9711504fa873bf2cb9746a3289c172f91b91f31b22450f5ac33441f0f65e062c03842504ea7a53b25920d22a4e3ae5603b0cc54767e4188fe85fb84761d92f5a38f09be35170aff3a8a0e731c7ab202fcca6a272fce944872a5d2110164607f6a691c3411fa2504557d1ee35a7ff7a892a8d15ec11fdcdd65a4208219becbde5de3b860a210bff0a930564bb489cee851c25ce7c61c6143a9a20e44b3a912d2aa9cc578f532c6990c38d47d0eaa1cc42f3505691e9250ef2543ae90116a101e30c656c0522992d0ac6d92a3ab2519d3e031267e6f41b88424a107399cf7b0f6319f5a537185e7bd3c3684676fc70bb3f3d8c6768473decb6bf3c8c3ee09e3a2a41779887118d09c8578c11903833202d326d54602c65d51416bd0a9441a517080003de7268b37c6f2f14e1409473a6941285b410c4a51051480ba629d9f110f7271968479db6f022c43d55447798d3cba028a48512f46419d0c61af91830c69a3e3dcca7c59a7eeabbb1a6fff265b1a68ffa6cace99fbe1a6bfaf8a3b1a66ffa3ad674dffbba2fd66459d217b214924432089feebdf7de4c798032a84a20f924666e6470f2dde00af1a68b740a3a774ba0ee01f76344d2ef66b9ba76cb6f66d9b2654bfbeeeeeeeeee6e0be6d0ee8efd8ad49312818b3e381984a594d8a249602d27814d5949772b23f5f252e2c4a27a148544284cff8dde63bfbe249f927e9540199b2dda20d7030e65908fc469225f3df248a82471be595ec9386ba03402c197e40976f54b9a4e754a297c750218dad78f40e284325b134a39036550aca1bf67e079d3e596637f82abec3d307a24f3267095f5c024d1e9aa6ed10fed988e8f4126433b988232e8d16502794c45ba456fca1e614319581989195f10c98af563b2288bbecba104426fc1114820b4532581b95c41162410fa9b28440a2920be64112e1914ba6450e89241a14b06852e1914ba4aa00cea167d97043ebd04a29750623f356671d14b2259450649214bb4cd6d0e13a5aa24302ac7abb0c4d9f27caaa84ee99e65599665dbbb7a72634e3a5fde331fef4d0ef3c0a19a655f98cad9b52f4c65ad7b9438dc439cbbece1cd1d186adc17259010e7ec977efbeab5d40130cdd2cb1ee3ad41b3357f6b507705fefce759f3785e35eb95cb830973c0b45919015798b79738a94c4fbb50394dcfe99b9f18ebe62e15ebf44a0f989e0216bc992c0984430944be7e3aa048ce64d1ae61aa2b72ce9928a448aca1a7d7e2eb84d8a2bfb929a18456ebfb6666c209cbc09c6ed17ad40181ab18758bc9a2dfeed822d3d78e0a7e8e77584172e30e2ea03cc901468fa45f3d40fc2029094b100eec7003469f435fc40e818b3ee4ab3b5a32c2f3fd2b84e955b40aa52cd7f562cc40483d309d9bfab9ba951dd7db0a9ea7ce47d810f8636564aec09aac2cdf40cadd319f2b8443699457db7b602889d3e5bed442e2745b6448d3668bfd89422214b92fbbf40be6bd813a3ea7a7de037b79f8c37c49baf529f9509f44fa5e3ed555dd0bf2bc3cbb41174231588449a16e0d798fb0a1d509deb57b46b4156495c9caf6092e92381d1812f1f223b28d25a888c1785c00632f7f015d18e2fef2bea442e270de397065a43b770e5c817b17b307aebaf7e42b244e95699381ab13b49edc302559aa9038dc2df7ad4002e9dbafcbbe28814820f22f5f6a26401ef5c40a5ef76577056f05fb80ecb6e35051606994b4051d47624c9b0692405ecc32d62f2a81ba2c518e21d4f0451d4ec8f185180168638839b0c8010e5d50c10551aa43fb56f431d77316f46ebf5095adb596ebee792dc8ec8123e8b20557e87e99dc77e7beeedab7e26eef3d5981c4c9c0d509ef3beec81a187a5f0952a09dfb4ae09ddbe2042414d1b4735f9198bd18cbbebd53dfa90579472e79d93eecb86c1f6e5ab60f5588b12634657beff1e5af1790ce16f7fee438d335fbee331d7ff2c964f5af6d097485047ac951c60e9858628a1a54a2161e4c5182063e315822d667b202895302c36dfbcbfb49fa757a9f3b7d2fdf492275abffd20343fda65bfda1d3e9a8fee9053c3d64bd3cc2500f5fbe081be298dc0cdc59fb0a56022df41db15b1c772e86572776eb26880d0c4204cece6580036dd6c92d26ab8bd87b20ca0a9647a0349aacdeeac0e176eed94369c47df7768804122f440289bf200324900c20d191b92787f8d503b38f9546c19748928b34924716a93461c8e0708c91011c8e99c3f115b33841858aa22c8660a5c9ab668e263a9811450e8e2802161f63fc8df1b101b4d67a31d56e0a3a51c896e9eb0f124ab38ad4bd0746b773dfd9ce3119b2e00eb7b2da996ce0cadef7c0aefdbe5dfb9064f40b1cb3f69d7b0d643264cfa47f77ee99cc5f7055c19b1c4a294da2d3ca5093b2bd3ba52d059d2884cbf6f5270ac129ddbe2279954bbfb8f78f98ee813c26d379b67aebc7ea967d89ead4c72c73295ba150faf69d12b8ca3670451f613b1cb8cac055f69db97d45c11b584eb76c12fb14ec75ec918028f44bc740bf04ae320c64a51fe15e027986764adfce633af723dbbd7b204fa9942d21a25dc7fb4ac7642a954a2ffd6a9ffd2dbd7b5cd59b3a90c7bb29ab5b26cb22b1ef6cc9c674ba65b9204d963d52129dda162efae0b40f75cef4ed3ba66fe04a7e7b0f6ca7f4fef69d9667e2bd2fc19dd97dbe07263d222d1891b9be0211b61d45446a9f85d94d56bbe2b60ca6693bf3a76b3f72ba86cf93fae947f05ffe02f268c7a8afc2380df52544ee75505fe9a4525f1dd1ae3d435d9a9d8e6f53a9bffc887614c883baf6179067680775ed3c2f4f3dc22c3ea15eb454ea8b302dc2b08ddc77fadcb7cb9fc9a25ce7954c974ea4cf64352c6adfe96be05715bcb09cb97d114629fd0dfde95934ebcfaea7ce0b44bd92ada497f4b348dafee6f4280ac1c97dfbd8968ba50f71fe02cf2e70ccf50480a16adc99781cb8d39d097e07aeee4d8fb0ed937986cc75b77d336b5f96bde373d4fb4e770f6432b49d097df70ddcf18e41267def3933bbb9563195ecbd6f75cfea965d5d50e7ddb202562a9365373bf07c58a9e8f8d0b7907d7886bccdf043a7cd9dc166b77427cb3ecb370c5cafd8186a9665b7fb6a965dcbc0150afdfb23f31ac8a3f10cf1dc37f7ed3ab85bf54e56fdd5f191e1be2f83f6954e832bfa7e0f0c5c51905270476392cd9f8cceb0d2e9fbfece7df78ef6093299d79e3357d9fd56d9ef6dc59fcefd62d71ae9948a94698c2f9f18e41db021fb2617e6f9fcdcc0bc37b9b0a9824d166c4aeed1a7c90d2ce63f17867a5819f07361310fbd05fcb06031437e6e60310f5fe8a7927904894049999c6651a4280427b71166654a737a57bf4aa7cf699d7ef1387d927ecd9cde64fa301ea96e7f4c1675b1268bda3a6cad32fd799a2fc2faa56f7a9e374f633ace993212cf2dd347afae6ab567d1bb8614a1dfa1d5caa36913d62ad5680b4acf4d29a5acd2afd46cd1d3148d39e0295bb1865e7a1f01648b7e7e3e4d0e8b12016129479980f000999e0359b1869ebb61496da19e034d88f9ca4b50a77bbe043da82f46c7c76775d37d781c9732bdb7a998c01d73d491c41c05aeea518f302432de513fe21d553a0fea289067be04ebbe08bba9bf745f7d29d577facc07d31df59c79573a9e573a8e30fc45d8ea8b301e5f84cd68a7c3dcc727759feede7d5eee837ae993f956f6f81116f3adec4dbf2bfbd2e7ea16bdf7a1e83bfa972f762bf5c56ec17cb15ba7aff34e474ac7204ff7d2eb77c4bbe9f643c1bb09e419dae95e3a8fe9de95e7ba6fb5a4850a4458f72544ea75ba8cd52dfa1a077d167dbc97aa95c9a22b7b17ccfb5616d461750bd67d11d609e17aa55661d52a15e9d473e74b0db4bf3dc21a5ca1909dfb11fb0de401798676b6dbf370cf6e4466f90a44d895738b4c2f270a52e79e933a3efdfbad562870d77e64fb0579ee8776eeb7f368308e3efb62dfa8edc4e1cee475c933c51466b95af5db9d7073a8d386d2a44dc7d8194c7736a6528e075415638cdddd7d43e96d1e44e263476d2ece9412480eadb5d264ba22121ff1a53694ca95b54229a52c9a158f19954c0c0c4de9e60917cad9b4da4e7e25642eebe4079379e45476658eebe487332a735e27bf9bb952273f9a39130ba9159540e27b64123277c2d854f23018dc8b8ca83971a69c12c8a9312d35a554956976bb5b39196b68d36ddbb44ee3eeedba9f042b99674b5dc599534e09a49b52109b32dcb44ff3a56321fc0196ef7704b76d05fab190ca8fcddd20a65e50276c2a795d6aa649eaa5793aeb2cd74bda3c9dfb54372c392abf138db1e6c65ea9e3b0e92b799f8cad3aedc6759e473579754a38c004ac8e76b7adb260939a953031e1cc166605ed6e9cf650d35621b871c668a7c8aa302be4bebc54eb3aef84d9ddcde33aea751f5742ae1438e5fad508a4662a909155272acb53e2c808a43ed6179b4ff81481bc8820f7252763ebabf3942b0972d7c8aa5576ed4b4b87806be5424ac9715ce7791d57a55e9b2b29b8cb755ec944c25327e17b216104cf084f4f900671b946e8e119e1e909801e349e111654ea2300011040288192e4481f86086000178ab3544368563dbc13f00058de0f166bc56a754849646b761e90f3f30712145670213424d6cc5f21648c478b005d9004923ac85792961b87032c7df0f07e0214a00b5abde4782e874dd34e5e14e04707d4adf94a850492507ed44820f294407187f84ad22d1ff2b4828b05c4d61c22444e2c1a92032ccfd5f098f18cf0f4344096e11995678404270cc4d54d89231f2f4a02998f1e9e02cb1054c978464870c2ae12ba3b6d64a5e2d2584296c5c078465850e90128cd19428580eb904abd40401843860c19121b105ff7f38249a217396211a5d82ed0044a62031c24251363278f33e46c5a53f645cabe12274649e73d65ca846a1b6eedda4f89fd680442eda3cd7405f9a3b22b3d6e8f25efeb382ab1e76ddb26a4fb91535187d2eb6cdb86ca42faa92d66995b9077c89e4ce33b8cd21e37146cf8d4c5e4f8d2ed64d7757793afdbad79ee9186fb3629a5bcf714ec08c43e469bed0a2a654251596ee9c27cb139f68d4a706aba90e51be7a87d314eac04c7749772319d29654265e612985e35737a699fd7e99706ba666b8600d81eaef27ce1ee74714ccc7430a917542a9b236ca1522251f1c39d425a9882c0208588700e2512b20a298fb284a3053ed062471969f460062cdec1c30f8248da82049014b0105f91445956e10150d64ea78d4744cbccc8d9ad4724dbb2fd81850a1918654a1330451ab97bce2b91b226ad1d715c2f8a6c14230833945025e4f938451509b4d1035212192f10238e184517330061e50926a61411840821e01eb047a4e518798641642215f1502a438a2ce8bd31e8c8f4d48b2892e04ea0c40e36d0e24a1a314c8185ae4c291165a091c3113588f1821391de5319641162898c070f320a27609ed73832a07023f7c8f4b70920caa8830654243147cc1272bcb1830e2f6cb1630ed1060d57460145115d670137d64822085464107a01169b10a28635ac3c81811b38c0e69435d0b0b50179665123cfd97509d041080829705851441111962e414fdce0081d888019499d6ec94f2192ecb072609c658a3ca7c032852529d32ff2b4a1541691691d9926894258a058606ac2ed249628f2a45dca08da4d333bdf19a5b4b36fc26277dfc07d4a6da535247d84241c76f5315261c4946cbf0853757b54145147b6df8451b08b35dd069e5de7199145e81fae3efab0b94ed59f647a4ef66b95d365500a34120808262bc90b701891327d920ec4d7e917882dfaf89910735b20be5c366164aad3af075c1ce4304691a3ec529b10852c892414894296d8ab4e884296641f04a29025f7a1ca4814b244034004a290259b0f0944214bb8004c200a59d209800251c812ef31d68403c8f4f40f884296943e42ac7921d6d0c74fc2106be8ed27638835f4d9277d3e2943aca1d73ed924d6d06f9f9ca1a3a1864c2f65904102352863b1861e049345534832599c0db82f3f04ac37c95cbf9511b9826ab228ca4a926913ba6e6e268bd21718ba26ebc6956990c9a2234814143894b124d3759344feb0543f240e8d2a0aa9a79f89af6b52154d16fd8906999e334df115a37cc1044aa06ec53e2593954402a1a61c91b8d909c195728cf6466cf5a7c4a98f9cc48143e2c4316dba78e70ee50950ac7982c3f8048712883b02cf679741fde2c0153d2773882f99145bcdbdf40e1c0a89e03830c6812e98cec3bd3b93a11d7c0cc6ee1e901cc20d427c491f4984dc2f7dde77b5609b031ea22f112056f2c0ee1c141c4a9fec2e08c5a30c24717490384f244e05fb2b23326b79843ac2a1f40192484c2ad24a16ed0cdc9dbbf4913f5188f6bea421da482e930584c8923edc3763dd6a19c764b50442b489b12fe788ac7e5f5026c59aee82b084024aa0c99acf40259305ba208174652281f45b281285c82e27c4979c23b6fa37372594d06a7ddfcce44099d4ad06ea4b21202853a8d04f0a0149209dd49f3f495deeecbca059a6c7b3452fbb5f69723fc6546ef9e514992c0ac310380c92a5e020d3868411563c66327da8424caca15721c69a5006658525afbc72260b29962b06dc0f5926fcb8912041e2b73222b303e62b46b6e847c8f42c7a170dd21d15b808983359946b8241ec43e2a42410da31b6e82da8d32dba051b65338091a6d9e03ccc01f29c947382bf3aa1d20a8394aeda4e41bfea91f44baef32a20fc5391729555d67bd319b83fdff5575afb22305f1156a3add656a36ec34786a25ce64fbf528fb56bc6662cb4b79fa57fb5888eb624d591347da4119738baccd8fcc97d3b6997a4389228a5540a8181638679eaa12b26c5e83572d3f9d3e36bfac8f593b1768263d2c83b71a96f5561be550557f5d935eea43a7eb9ea3933fb76609e7afd508079eaf508063894b19fd5230cd68f31e7f123325f813caacb9c07c833b4a3bacc795e1ef308cbeacb638e44751926431ab8937aea4c5e0e73096585fb7ad5875fbea1fa9576d57b602b0d5cb2f37215c824820fb1ccc30fdd47580c4c55653ef9d3ad7e89f5eb05bc571a751224313caf6384c6dcd6de2f54a13e54217ba8c2bdf65085eda10adc4315ba872a780f55305d85d2b9090373020a0c4fa92fc2a6cdc4f918fc4a9eb67159756140be5e4029740a86574921f97a01859acb6409f54b6e315b7d29b4a54a9f8b0257d0ae61b74c16e70487d66ec97d9bd4cf6278a2b470d107b7ca7d50e2f8a895473d1759b701cbcfeeeefe0d9149d554269b3e8d70e9dbfc0dcb1a65df3e16067d7e6e60a537b9301f9f2687716fe28275f76e02e99b5c58e9f3bd7d3f620d6263cfc8810c365ed1074b84d1860c72d0011c4930af0212b63d67e24f96be568c09084bc8f3dd176309e12b4f048409c8f3dcd661c1312c8d1061f5472381cc2af33777da2ca85a5f8a362388465bb45919b1ef3788d3af9ff11561159525d03503f7c39b70fa3f269836ab13ec5d9365634f9fc2b459dd2e27052c4c9b98e56317084aec27fb5638856b5647108db2edc9aa2e0af6f9646cb2a805b241f45688de02d15b28f4f66014627500407cd91d628b5ea57a79c9e133976f812490ae8a058afddc20a892c0524e1fb44f7f8993cad307f8c99a4960190ae0ed8ebbe66fe6cf9a70d4a06e4d2ecbdf695b90393bb941c312f4d4a09eb64f3b5375b89fd55a5fc12a44bf3a65b2e64c8e950dae9f4a02d3c74b1c2ff77db088a8c0212956dd26385541d54b1e4134ca3573f55bddfe4dff2c1f3f3a0ba27aa55f8bfa95a85621aa449528f75d3767193541cfc9223d6bac0a0cf2358298c4a19916e9168d6de810db504223a74424246c6c63c6e29f6f286351e28429b04b3fc856b6e4c34806fd7d21c75789635fc8992cfa0a466ec6623f40b11f1a62b1582c1693b108e81409114b1037bc807332259b60e6186bb8185fb6ce39e7b475c2a45e26aa4f139b66c9ebb8b969f366b81b0cbb158dc0692bed9b9e73ce97ad73ce396dbdaaee9e73fe180355e5d7986613d4c659be2757abd3adf9396790b96212afc054d05701fd89c662fdb98aa00e0563de8957600db2ba156132272bfe32176b49ba307be6bc42d0b4484d5e0af617e36bb55d29adad6989c1616a5a6270a031b3f722c5d8936f2c99b0b66d1a195946eb037ac2a627da75d36e66378d8cda37b31db5e184d2341f2f507d147da9d4b696a524e0192141aebf5f8871dba048636c78f94b4adaceee750fa82006ad97a230b4376dd3c8688eda904aa562686fdaa691c1d9d0b18dda10d3c57844ea63a80cedba6937b39b4646ed9bd98dda2063a45fbfc5105d2ac6a59766b68a416f66ab1320b6e8de79f5d28cdecc562757d2a657ca1b5bc6a8934b2fcd6c1583decc5627b66697da9a8951adad99137a6d7572e9a599ad62d09bd9ea044609a4d9d4f66be37e9570c9298c8167775f6edbc6654131243a3762b95662cb89f8dabe813226a5c8b108c48a745de7554dd3823ccfa3444230e4691fadb55aa954327550ecc6e58a75c923625fba78ea04992f191434244b58801ca1e4ac1571641409e43e06e59fca6641518282e6b51b83228fa4510c4a3d3ee7bad94b06758d38322681ccdbada98d81e5b35c99ac298b741081ccdf692485f27422d6cc5b19abb9416925d6ccc7dc804f37d334b9a5d80803c7df1cb16871a29b8b66c0f6a87ddd6c6da0cfd0fd7c130cdbdedb9b6098f66d035773dbbabbbb5b9b4912633addadf54bab7e2abdaf44c6b836b54ca7babba3544c8b74ab29c77dde4f5ff719eddbe9dbaefae4767c6dfb12fbd5d4e6f7b9c0eed75a8be8a46082eeeeee5a6b77777737fd6aceadaf75779da08ef69877641ec3bd7c72a63eee30a7c77cf8f593f9e59330fbc9ace3d12fe654f45ebe38bbbbbbbbbbbb63769ab0d02b380b3bb23c9d4066cc70cdd082d3b2c466c618593bba148b192d38007de5b0cb0bc8f5d70869079b03a46872fbf811c466480e87e43b06516e329ae426838cdc6550c9dd65782106d2c477087c053f8a7cb92dc2c82253faeb031bac327d06461837b432bdbd5fc829c07041a6148c2c99be7a44283e03a7e6ab63b1580c4a97a2f1f14336c9619021996689346ec9d4cb618ce3470e631d3df29450224105a55f647afa8b5567b9c8c8f437394574ba0042a64792c4874c9f829221327d0ab8a041cc0ba29f2dd32662e9028d202fac10929269c4725447a611cb1751647aea11a140f001c65bbc20cf4f0ac71af2e2dbe11b5f118605d083376e07649af33dcf43f3ae34abd32a593d3acf8ef0a292c1a899982ccb4c9b29dbaa649ba9cb36799cbc771f01638cefe1ee237c12f6f2f1accee3a88f676867751ee7a1b969a5fd48dff423b37add766e665956b3da73bb8f7927dbb60aeaf8cc5cf595e948e6799cebbe55fd08ef81791f7ef71dfceef51dc864a8823ba58f003259bdf41eef81715ce5b86d05b4a58a0b7218440a20949002081f29b9ad569936518a1feab06282cc758502c63d402634e0aabe37706705ae50e89bbe33cfe3f33c5efae9d475198fcc9eba9770878239e12a3ea577afd854f2e29d2f5162f7b1890be6fd07c3ba298f02559620113e4d2e4cbe0986f5c3ceb1458ffbe5f3e382f59b6098774a4fe7dbf3c0952655498ad458e99426a97a743c609e3a5dc5d01745b9a6fa5d936ae6a5f79879f7953ccffb8c577a0f6d2667caef73819e0108901565597d119d0ca460025a84b4465297ac76394a8a23e9a85f41821c05390a12647baca12bed0424108845518b4c2f8bc41a5ab4dd8416eaa5b764a5813a33251d1f9f98afee03731ef58b58435fbfcae5ab6078f5a86ac9f482f528d65c1466ae3a92d269aed3e3a58f67befbccc733f3d24d8f8f46f5c56e0d0183cc7c43da4b1f4fdfbb59f5d577545f814c86ee99f455bfe08ecc79804ce6659e3355e7f1c994beeede77c09df9ae33a7fa22cc743acc87fff2714f7da8cb7ca5c792cc5977999cfa224cc7c747869e580c751f9fd3f17d4aefc4982caa5332e113eaa72756a45b14a91e1551fad2934d141a7dfa29658d809c524a2adff2f600d3e75823604dffeacb17e9170563e36ec5c965c290a38fd0c6f276ee6e81b5df57a47e652a55f66560dbb6e42d1e2501850f599061048b971e9188bbb55dfb88b8ddda7e7b80577147fbe5398f0557f196037778327015c155dca9d75eefcd4a3c590f2cbc793b977d31592669ab660ef3ed29f40b7f7b0afa85fa76b965db697bd94c9b8cb283f4384fe6e3b67bf4fd992fc2e861beceb2ec34ebcfc874a00ea5adfa222cc24aefe9efd0ee974026316f701599947a60dbfb63d19f332222bd9507738e76db167f2b97ecf64734ed56035791477b06ae62846db75fce16dee46ddbb6edd4d3c9b6da712a26709697d06c82cc4ba807ea64194cf7e9c88029f496e56d3b927ec57c7becb845cee7e527e9ad50c84ebf939d824c86ba3329812beeb4037760de20931870c5812b14b4d31fd14eef79e8b51fb92fbd04f2f4ef230ce69ce9a9d331ccb9ed43b21d6f3f6d476d7fd99e326db09e582cb66de7b62d6ed45ba140af1d49ffbef44558f60d5cc1c6e83cd5c0451fdcf67ead60f72c7cd38f54fa6a02578f30f95d9ca4ef53e87b9dbe47d21883ab4ee61e31d39c36127eef53e8178f5fd5afcc6fd24ccc3502cb29d2d31e3d1d1487d2ee39ae47844daf02f83bddf4d15cd6e3cb406f3a09abf378f4565d0fe9795a5ceeab4e27d6dc7337e025de4d882d7855c50447bbe91159c27dd5813a271a1d1f9f99afeea33a8fa7708fa45ff4f73a51c8cd3756ca4362cdbd8a093c733824dfd37b1c09cd4778f47448a85c48f846e8d109e1ba0658b74cd6bd5bf23d773ad1609a8f87f20c75e7a9c7f5f83bf5f81db8436f5a7d1186fa224c5b7da8cf7ca9abbe97c77ca5cb7c30e7f19d503f7d2b1dfc0a4418369dc71761599565b612eaf874d33fed9aeef1fde91e75bbe4fbd43dcc054af5c4ae067eb2eee571b9c0b29efea27aec340121c2ec8e760dc4f61a78bbd53044c0d2852eb96a9fddd99e6d9fdebbd5158830150d789e3e54b140b32fc222855d29e79cf1540a579063ac913d67151b304c8e58ae60230ddc9951c7155dd45004e50a1c58b942685ac12f3962b98287a9c5153f4d059be6a043609c86ec82f117f3079f8aba087756fa07734a4839b03da2c88a28d305389523162b7ee60dd834470381711a34098cf2820b6cb39864e03bc51c036f4e483a3047850261ae0832aa0822081818555185b5da8401e38a15188f70831b5641403d8ee05144e3ba31c061c675e30a7263c49515161b74500101c4c375b1c040838cebc615e4e667e676a92146882eaa2d4f6082380a02490667b1432a29c6759fc082c38bebc615e4268a95285960f01336b801454451ea092176381901f4f204141b700c504f00fd98848c4e4fc47043498a10dec11c35783f1c9976904443170324624065b6620e2f4e63861f6236146d51439e1264c50c425469a205322052220f6124444326e2c787a0241c6ec85292b0ec000627b988203a7e5837ae1ce44628d6ad78d9b8e107a26e451e20c18051003168142f72e89a30601414b307b8bbc1bc037743730011a5e30022072d8080a1b3604f08f3094cdf6825b005422369692fbe00ca42065cab9048f866996f60ee0654075823923e20623a81b31cb1fc10b4056b3962f9e10530605828b85066c9c9adf391ea702a1aad4ac87107a81b24731e2e5d53274f9dfc5499436d91b954bca2820969e69125c8424a7e314be6646260522f2fa853662b6d1eb81871624f2981c47331d2b6944a39e79c734e2dbdcad3049a4ff39833dd2a2a5363c750982c75fb86d2eeeebeb9420331d7478f03979c80412226ef26225dc8424a226165b66acd553a1ce0572ece80c10537c061c3131dc0ea0f1974308428551081c50fb05aeb3ba4ee9e2c23d7d77bdd1781c9aa47c951eb7592283141ce000bf6294c1beb651988b7c919d9aedd848d03c3ec28d0a696d3f53132e0bbea8f69336d3290fa6005b9a9a679b281bbf1b5de7befbd5bbd96afab09517e25baa1b9cfd201acc317ddfbab81a1eade6d0c9cbd7e03433c421038bcaf0f2bd10d2d68826e21b93f26f20c2c433f3017e40d4c4441033644cf55a27e7ddd27ea578455b992bb978861cc008a2662d0431c4be4a08223d24032a2062e50eca05588ebcbc3dc81b3e774f68577857ef42c1816c936bcc9597c16f6e4ac43989b5c2bf863b2581306576293a026504e5819c99952601122160310175760b608aca45f3a462bf457d40e814309c5d5adfebd5d0d4645596691014a1619805cfd52a924a7244949bf22eceae0d8ea639584416cf5a514a1044c4289354b40e9cb1f2529f8775fc0e1cd4d8425e2e7300bc6d9a24b8850504699acbe09314b28ab2ba04869149ee6fb78aa7d751ff3f6f88ee67abbf2316d3cd7b459758f995b71ae7ef9f02107963ea664a12284850cae98c28b9ea12c340c495143d2141b4a1f0b8b22dd77bb283705763071a58932ac48a30b0df040063ae812a50b243736ed4a992f927d3f7c18e1234608226e50c41a5ec4ae88678cf1d386166bfc6c7106966508e16305094b1d61c01c1005961940a1450617ec009b77dd58283c82206bc71823555d33039bd1dd3d678c34968438a594d27aedbdd7d6396777539517ddd4d6309b8ba65faca1e006725fd68af1e468cd322e2a17b66e31bbd66e1cb6682a31c6b8d6da8dc316b5bbbbc034ca0cca6aad9946bbabcd7c5148983d00ccb4565b3d231c08bf13be7153e6e2970b2a59d1a4c8852173514c2a52a24a9a4160cc3be79d36e0538e597cac28820f152db02ac72c3e51b2f8bce043bcf45631ba170b9ec9314b0c5c620c465022ac0a9478b36cb3d90bf8de23285cee80d9000a172d31802dc1a5a807580db85cd7134de6c5b7c34c602e493c815129e2e2c30e30268e4c34dc99658816040168ca10863065cb0072c4428416584555bf74b16e54f2b254aea42da63cd5b823cf39e79c73a6b2c0f004153138c9fd4fc5c619332facd1c571c409020f3c6060801c763134bc78a189dc27c123d2780d187c73ce396d46a0436867891da145eef7c0734e36aa909973cef9a2c10ab80306b9bf82e30998225891bbcfc323d2375ee10697120acb8b76118228c08c0a8a8863c71c79ce39aff2884c35e0f064587664d1aa376280c139801d2ed8d2aa158aac0011f42387195e7c4007357431658c28b82051e28d36e69c13c60e4c456e551d3ae4eeeefe135d8a664b9ef31713414a9e5711818e3c8f3a123b8ea0b8800c3b7ed87204091cfc9071c7143c202272438e533c7944a8a4828e1e884cd7e753850ee8c0820e2184e8d9810b164adcf1859520fc00d6dd3d83dc6bb0913b2677774bd93bcc540f0a30c70cf2a4e327cf63d559ae1b2ca902e4f0c71086c8f343e822cfabba2891e7cf9ac327cf975c7db9051c544adddd2dc4935d8ac6c72bcf5facba1c6d6496942fb4e040099f2237dee876c10fe6b0b283306ae8c20958cf2472271991bb5f2402dddddd1e117ab40a4395c32063c48184107c840084108880458e2e8280459e376be06046f38850da81394c49118421387e90fb570547504a5279931b8e2472df7a44faaa6820c20c7e6240a207467054dc588207666c39030ad8d19c436a6c99734e1a20622af71b41bcd1456e38a4e456c537b27477d3f860334248e3c38d29bc480a4a6a22cfff8d17e479caba54d540102277777723a1472c7049a1a916d2641f39fcd1051b3d60438c368e70638635ecc8f3d3d5c61324259ca08183345a9003c50f5d880083157404013623529611c94422cf27f24c61d161983b70c8ca52c5e3898a0729205001630d1c721faff145ee4b9514f2658c1cfaf891860d5aa431861a4ba851048421ba14953736681f92ea44d59c32e4f9aa135bf30d86b7863c1f39703e4a95ec215751b39b755bf6ed6bc185ac0de150c64a9f6f03975e2afdf49d929d3ba563904909348137567a09bcf28c19a821831e6250128bc15c47c196fbdc3bee71b2c2ec2c74b9e3c090e6ee1ec759fbd8ded7c28fee76d62d371bc54299ac693b299c7d92e75fa5c07dfbd03e21808f3db77944ec8dd53332df359050644cfe74500765369115e26419a193d5fd15a549a6cd7c4918320b687452153628082a398c4768d491c3b885841c462e31298c71d0acbbfb26ccdcd49340b747845e02b5808b3eba548337d2efa6f4f426c8537048be60d84781ccf436fbdaceebcc2399cf29f20667efb52fbbfc26119d2bc17869a469910f230efd9c728909682622b63a2261f51256c19f0e56bfeacbbc3ac194fba64cc40aba63b722ae83b3e7a6903c02430a214d23a21009b6161f4f704881a2bc3d05a23710a24014880251200a84baf784b10929caa44fa116ea5817d1b18ebdfb06fe7e664ad0586b3f83a290ecd674fb2925da501a22cbde9ac046ba0a772981e10fd905e581e10d2f8764b105c3677b497f628dbd07865c1de1c137bda85ff108894b5214223f26a7f3c060fa095c1dc13c2726a69f40ece998c06f6815272b0a515fe9a226ea2ad65a6badb5d65a6bed4d4e111d24497c3cc1d6fef64b757b962b15cbf1934b9bbe1e18fd49b2dd25dba657b4e9c53450d3344dd3348ee3b82c3914a96ec8381f381c2cb64135ef9abff3f50c3b4a4094871cf2bcebcebfa59474981ccba59debefebceb2650679e23c77c8f3ae3b7fe9e2ce711bd881b40558e6985599052bed391fe58b1ddc7dd0c8a654d5612a1b3705caf41c125273e9a4ee4263f4a7632dd4b17ed5d37711d1a6bf88aca37e55b089fa0fef8f8edd7b338cad4a55a7e0d9f348e24cb08926185148834d833e4aa289ba7499b1f933812694195454647484c4254942e921aa7225724941490afa4561577e24d0cd4d4e119dfb7882c329448455fda2f10acb45d4d3680acd2ae114a24059109c8c1e88421a0c650c59e24850fabc16900318c0c2986548906c5e9adc5d303c2b4f0b821d48c15085f3a4d743c232a75f49c02b57a41097a45fd227b6e69d70791225c9f4640cb1664a24e41419439e9753f27c366b0fdc121370f65949f608db8edc679bf608d3aaabfea6fef5acfaecd3c96e0237a5e6068c5a0b6647e49c19bd0a4c5806baba15a336d87353b8c0499ec9710a1e9232d79dab5f1326c11c09c4de1e15854c10166baccbeb38cdf4a5a05b25d0bbb7c39853a9b6df5e5089d6466604ce40ef0b5c7fbb1575ec91d8e7d817b1ae2978fef6cc497dc04927ae6093a4a1a69e634d9bc89aef6f7eb1adb4968e00546bad3ff0c9b5d64c4a4b25d0939a53e4e27eade8fd91ed573bcf045774b347349e06ef7610c378b8f368bf5703573bdafbda6bad2d303007ae76eee7ef6badb5d65a6badb5d60cacb5d65a6badd92acb6c0fa25a6bf50151aeb5d65aeb118565960ec13a0ea8dd11755e9957b28d77daa4723c0f2cdcf09065b75455030e25950c6502c5f840afdd5e665f4582fe8acc326d680426cbbe3f7b67db401c597163e292b57e0e982c5b3b2f67b26c0b067802d53981a2109beda55183abecdcb7dba33cfbf34738f076cb7e7b837ddb0203cfcb2a13680675cb5a6bed75a450b7ecb66d27620365956e59fb9dcba09c22560a6596ab5bf67775af149a2f9a9824b2228b22962d45d93e8a91add11d39ecaa51163d18c561b31c7b3bc401b6883c64dad4cfdbbbfad5b757b82f9a9c410710d8c621441bb20e7c7f500712cd82352fda086cb39836c0770ada04be3ba03f60ad4afb802d0d6619d822415b30443771460c9c9ce1823ec3a7d5a043c2a1050e2e5ab840411a023ee2414b1632982ec795e8a0e1078d1dba7c814392154ed8006aa2056c74a9810d44515e90d728c2a2450b6e504387dcc615357608e2820720206521050e998d2bb8bb4fb171af2c815739629182944397137cc2228a1427c29042d486942957a4e4d03d9012c318d80d27882e8d416e86dc683192c901a58ea8242c980a9b182a4533100000a0008315003020100e874462d1681485ba6c1f14800c76aa467450970ba45110c328ca18648c210000600020c41820a9a26d00847b6b0128020ddb83f41b19e8719d962746b226f59aaa2f1baec8fbaec571efad7439ff382dcec3a0cb93b35207a3d3e90a71ac67fa6383a2037d13055532547de8ba807ae399f943884c38f1e1920e21f60abc26ac6e68422429729f84304cffed49c3260cd244c79d10d55ebf86a49426b6ac0d0b03f6e1d54aa67ec4a33e9dcc67e5116723adf2d2cdae1c6a0779268927d241efb5c250862325efe6bcf7d70aaf454b7d3cf410dc859ad54ad4520e2104bd07e89220157958681459bc4e0ba02b770bd81b911c607d831c707d98e345d92cf6e3387d045123f0fd174a47af91db47fa7d54b74f853d7e83c917e059594494840a6b807cbd5f98f1c0741719359a3d4a9be956e247d82cb060bd97665f14b99cdb851f48404567636ae15b7c790f9e0bfe33dab8868a07e298eb7c1ecfba5f580b32e8aa6e50cc3ef31a70181a916d2144f29c4f4dc4a11f25d1584cd8d25b93c22c677ab42515c482860a1781dc2ff276daa2f6f54d6c2e1c2ebd2fc70b567987cc6ff3c66a9206fe96b9b05bfce592ba28f2cde0729d862a0aaf8275c15b895aef932d38adfb4f1b68d3867c1504cd2b1897640a2844481e4e35ff225c70f6147d599767489bcce61254d7a50751fb9c533369a07790e1a63065c862a2e12251d30600ceaa2a3818b2a27b37c29b986c6cd0d7b50057c81283412283968f8e410b37326819c9390bdf82dcfe7989374a715f3db6f3c086e681a2fef8b8c4357f2999e8a0532d7e1fe40177d91e7db6a418aeb9d4843065fbd3e89706aeedd517a469123b4b7c19de14e4314571ac2ad718c152afe532412c87854a5bde1dcb6eb593bc9685184664541d4e8d2f2b5443ee9265301d852278af1cf129d09313e0f9f8724ca1043eb4dcc86f8e69dea2b014616d9fe47fa782be0c3b2c3cf298472de81e99c92de0b58b86512851b075bb1cd37a590380c56015642bce7f403e35d690e5ebdafc9c4d01eeaa31978b3dd162d56acce273e24d72609951f2acf15aba4117442f67fd71fdecd6c56815bc06367b9be34bc2d3ddb5950354eeeb51fb4e5cd55984035f5dec9f0320080d96652d15e1944277536895b158577b60a98cc11e344de02b3dda58b23e66e4d5e5580fa09f30b8dcefd19e6934a8108b5dcf4506c3101785dcb4e42d662c8ff2ebce2df821d4ea6a8d8b41fa55cefdbea06cad513ae55ebd6a68de5e5cb61c2f7c28e9978c57a5ce838c02cd661df29c8d4f5847e726f06c56db3e3e39a0820208c8176c803ce2ba89f2bf74605989309cdc6ed3e1eca5ce7a9e4dfb3f153426488c65a3cc81cde822dafa58272d97c6979544bb650227855bd71dde2917e11201135567c89481b5a958e16ede08028494c3cc18dea3b4b918ed1ee54eff415409fb197521e2133b6b192be82778f45879ff37878c2f175372a0dd5a94879ad440a4d7c9ada82819f5773d4eb96f7bc10eb757f44f49a8d650261a38107b6cd74a67ee718377f1669f41fa1f1d4605ecdf266a8adca197be84a95d5d83636d81eb4f59496d2a943433bc456d2019877b2efc731c9a7494b74d231d8ee2d3ecdea6d4cd5d18747486d0a7b0408c050723d4a6d6bb2abc63c05faf605cf03eb82068ece8a579d17f6c3d767b159be05eb414b7eb6f3623a84cb13814cde2d79b60b88c9eee893f53bf237521313ae28357f95fb8957c37a2d1a2d5b0efda89ae60ff9e1d68b43a74028784e7172a2ee8b47ef47b39529f94e484ace3dd580456e1f1a567fe207bdb36228fc6e22c9de4c75b00c87bbe0979815942aa8a524ab313e5a015e02f58514a651aa54de4628bb60b4892f6dbfe9059ac5218ff1448109b2a7fc1142370ce66d2389335c3717b0b074b41f24a3ac02e9f0d4e2033430991d81bf1cb3d2c24c18f92d033c2a0b26498b3e1a102b11dab56522a4e04fa6d5122e3e663f20e8323abff23e922cc493d480db50ff1bb7e3f12100c19325e542b8e9f65aab2f859aba2c3d272d7c409f12ac2b4e869e5735d25d635c10f5ca412948fb948b4ee9470228a7f88baae5a3c6b36c5b7a0beb69022ac8650fca09a2a88b92e9b52968d59a8be86eee706afb1834498fb0bfb1032f3ddc7030336f6b3fd1615623b42082730f66e4817c31adc8fe2cd1ce02446a370c5285233184556722ea27535c7a39d102b39d5224616cd3cf2b4c9d6bd732c2535c601f144cdaa7f6625b861f96936270e8e05544b5de440a7d34293d3c1649ee2b94e1da28de9d59a5e1e752f561a91b63e33d734655d93e3e8474de43e9fd21b082c682296c4e33ca9bf4171f5ca5cfd6eebadbc13ab7ed259837710e1aa8167d9d449da57dccdd0b9d184815bd1e66f833113e4d6428d8f552f23864ddb4fd2020461aa8854308ab4d4f17f17c2b2566673112ab813045f75f5a7b53c470dce66b282e06c7057bdead702ca23ad5574727ce718eec94fe8c69dd579a5f6089e5b176880f98a3f50c7171fa66f16de9ab7c3e6ae775d170dded8e62fde3ee89f59d6c7862e76a0f31dd41c385f542975b7d0aba48adcab569330ce0d8e1f94318335489f409bf11a14b69829f46dc54a0371225027dcba72d8c4eeb49f411d533a50007a8fee7ca1ab83eef12d15b0e82eda743b01553b4672144a121164ca48a78d8891b5bd290081aa9d84dcf30364b92592f147800465693408e7b352381d1a9d788166b44641fed08c1420b3e7cdeb36f0cc4aab99d2d94acfd9e4f1151d6730dda7c23ceadc56668736ff21a29e1f9de5222a6fbcb1a24b300360b35428747fcf3970346d9655ce086475947641561120e224e41a4964277fb568465d8bf0ae1c4f82f95cf940c72f7d865fbeea06ca344465ff5265b21ebdf9af22ba7c63dea76e6aa121009e6d7ef06480b818544a7fd578033fc827fb96357da2e49a67b64b20ee6c9c8b9d1bf875e150718de9443b92b52cdc9cfc0bb6868236897e43b3d7e31824ab3fc6d181262201bc85ab6509281376aa9c98f0f6c92440c5170baf135219f39298a341293d7d4989e1a1548f1b7ca67453cc41bdab1c70c9969c679c70b1864ab4e40174a2530d9ed3135417e63a8fe965de1b3e6f3b072c256415a385d22ea731047122694537262855c7b1237f32e19f2b537aae8e4a45402ab270c050d57973bb393258213f9686dc43fc82a08c9891c61fc229bbda8fc2d7bb258911496bde646ae217f2dbaba0707eba7e4f7272440a0506085e45963d212b570e0a9684b157a58c150581b5f458b2dcf445e0c8df41ef22b6bb4141cb95ba2dc8938905323305f2651d0cdeba796b9ea688260a45d0e684ffa525837103e67ddd0a1113422e1f5f7833779ccf91b0660ddd6085d94e138feb329069a0e69070bb2f27f0124c98968c023c5c5f9427d546c6fc19f6545596c2fbde3b2ac485cf9cfb27ea9c99aebe4236a1173bcfbac56ac85dd805a96caf94b81b0c9e0cd5a8de517e88260e1fbbb59f07946dff6d08db31999d182ecf6724141e94ce2ce6559dcf317de38287828bb9c1451af9ebc6019c25e814419a2f0763d5be84468db8584791c61f424e9c52fcca8d967d02b16ec10e29d4ed8d384a8ffa0bfe301205e6455ee798de14a562fe65d615b446bce63d88f06e2ee45ee49e87fdff83c3ccf159630431c31c4e4eff0eb83c6000f2dce07094afacb284a17ef2eaa52df9c0510a728e3907270f507bfe023600f187b8d84a8ab62c95cc143ce8545cab4aff640bc215052eed7b11c78ac144888e27049a740b91d0dd0991da6bba884540c2d44a313d7058d5ed9c8f6b1ff41fd3485b3124b06d00cdf256f620e004292c49bc8581a248f541a73185c8b0432841e97dd5ce6c786328812f40ff7f5f9e07c2a625430e98f4af66e15e3005de84fe1da5928713023c86c042357c83f22f25271f82ab1cc6614f229b23bb803bd4f1d3f24e23fd019b30c570eb73421e0923b7910e15076dd54e08dcf91f56ab7721d1d6c01c84ccccd5bbd58975aeedd5b52d9205b00ce67674e0fdda955e421fbc7065d71132f6ca35ee79262e4a5b5b4185f52bebbad82d555319f6bc7b919d5fe3012c5c7789080ae5d00ddc9f22460456d33a350c837f90a050c471fc1390038daa7e2629e496a511838f7cc4606dde0a1f58fd0251c310e28e003a060902ba1bb4f963319db6621b2164ead83ec4e47c990ffe3020ae255c1cb6b16773421b6f5eb6f7d488e36bd21dcbacb6435b6fefd8e09fbebd10c1a6b54b6ba977875244a9ffa4eea491834651aa2bcadc858da53be622d7da9993ad9e4b984ab887bcf06f2d991f30b9e9a8f11bfd9534e41b507a2d7452e0bcfe94eb3e8e5c50750f44d1df5dd9e754eeedd115392f70368c5c950bd081aea9c6d198b7a0c79503869a1fc149ef740aa97c94de69a28b28f449566c4e23e016721a6b3bd84422f28d36f5881edb61115a6c31ef5f2e4ee2d3135711ce1a164a09683949ca975072e1b1f492f8abaed120779bf375650f4285c5cb78f3821a49b6965c38c27e9d6446f2a7f0447de7342d07d61ace8c79a967a135edd5ead80878c451fafb21b9a84c823bd89d3331b56cc237f4fab401e194e5154b82b686929b08d4236491549f568daa1ccf2b0ea3e8ee73ba5760e34b7093a5867444be94440a9399b6a04200754d6fd44a3db7b8a3d245ae41f5f4fff4d390eb49274bd055b1ad899ec0518bfe492c2048318e08460441cfb9cae45bdb09527bea58e32428287153679a2c9b6df508560d4fbd38750406fe67ee3a1ab97eda2a3f3f059f12dd1d8da2f39a4e6d7e49571190e9aa8a9f093a40f10f35c64b5da72fc1e3808aa6000ee29ecc231d18a006fe8eb203cedde02f66c46977995797ae9615187922d1d6723f16e35ba639017334e9d992e1b3dc68278a6b36cefe0bab837c07af63f18405459c2d210aa2fbb0cd2090167907c27a0c230c9786048d176392acef4ff22a3fff48fb35c2cb324d375bdb2aebab18bf44b7ba52671eb33a0c25e6a1123fd329a40eeb4b7685eea885e9fce1f5a2582e630fe9dd35a5547b2bbd6546a594ac48a594141946b2577e31b1a78860ee89a56fe18c24da6b149c9a436225ebae21395e371c7e728dd9963310158b8927c7da97d75cd5afcf182c6d0034e07e8da5457c3e4f9b8b86acfb4a6323629414f09ec3b2e233a5498f01a543e10017a9c825a2a201098ad361e678c99cc6445c820dac609a05d13dfd9c9fbbd94012b9a239b6cfab35d8a9988bb594516f74ba30b43bd4a20a99f29f5ad561232c3bd4647f25faf0562479d7595ae2921a5a5462a8380dcbaaf7509e51d22a564c44f7621ce70538875f97bdc180949b93ebdf4d42c8cb8ec63c401145e121c690c0d0beffdc3e0fc16022f14e29cfda1ddbd543e02113a273cbe30853ec7de72510b2f9f4520c1233fae1f26625899e9679682b37016b9df3044d28f2d9b3262039b5fdf141d0c262e96dd9c9d513dcf9646935afca88f45e15cfad0ca44b315a804c806212209b50be7a6fc943df6dafddf3d7e2e34d57b290ee0d261fc4f60b7061259f0d8456007ffeaae4402b0dfbea0e0e722c1bc0b5776f9497253dc23ade308c5e93764ea6a57f4f444a3da0989d7c3779dec3cfb3929414a12a4cfad813b760cf8d7e3db2b81e23c3c6c1563a819c0f28cdc74a5d767f6109d52e97612db0ef2bb874678c4569829404e3fe12331cc5863307d37d1e9c064d20537b34a1596d6069d42c8556efe010f2e1d18a6092faeb56739d10f497894e1d02c83a4d74d217879e8a88318f8fdbfa3a764722b96d33fb28246e38b5515d08ba46866e070949de1b977e68f422e7849ba660f124f02dc4c369fa0ac8c3b939b07d9e19e8addd010e68a6add1024e17f88e610b5fa38c7a2876c04a83f1099314ede4fcf7d57127cb120bb433c362d4a6091a67d21fa783df68d00391815ba80e2798b331c6b47939a35cc6d4a2f405d3cbba5c6be75a0cd62b87519b6bc567b4be4b4ebfa588108b6339195a3880f97062b61d3a6994fdec27475f372b16958fae683b91f95b558f240e23a4eccb087d4d52fd3568e63ae22888bd2f40aba91d8b80d86d160c20f5c874310e8b43e9fd07c59aadc2a6084c2f51c657653bc39ec776d4df00877b11d00504b811fa827f5d26b27b03cb8b434500e5c8cc18dbb984613bd20e9d0259a1d4b1f2e4f7c468db50239c30456e5c8870faa60b1ece396cf76cc3cb39f172e66d516d68c33c90ba78c653c3c85383dc8a5b3307ec24e0d4f5a7cb616f4ee30389b82fa8b8ea726b32bdc08bc0016ada7b1b1a300d299d2325ce9d77561829b3211123815dc2ee2713031010591d6417bfd48b745ed8194b24fe1e1b67d26d6469a200ffddfa6ecfde2980256dadeb12fcb830d5d47e0478ce74351eb067e40ac3fb25565f00b373173e6547428b36369a697a944c514af595a4ecaea922f5a20d2857a48af8e28d73193d1eeb554d3a403c01c47e7d0bed9b8e1339a1cfe4b347a653722b3484ed2ab1ba5cf9635cd48c6f65a97a68d40f4b87d200caf4017c69c7b4c1c6128304acd32e4afbcb4eccd213c6940032dd3114e6824ff343dc08b34eb72bfcfb4fb58235227b3f79cd77685562ef7587f29132439d1c30a2c85e590b83f1a83bfd01b32f4e66d735c27027a1c208abe60c1a55a1a65371f00c508d4466ec4e98d479bf272819da0d60989524d3bd211e725d4400f03a146a2f468809cf404b640d2f69dfb021fce442d4839ddf6a75359927384288e4de7aec48675e8a9e8cafac7a8a36cc0ac1a2c9598dae8f77266453f60c1e3520317d105fc1150d1fb70305dbe7c7ad4e79c1d7325fbc1e8484f2a8640088e548063a3959e59692d07ff243642f0ed1641ef08ef68c4b80b5eb37c63ed4b6c4157d1cc74796329fa6928fc1108ff4851c049e79b9a34141375124ea6cd6eac3f6b8d736b3c370c8a78b91641991af87b0ea671b81a8fafe60b1d8dce49c8b79966c42f915d5cdfa375dad10c4029a74b6cf0137e6839b272030455fd571ccb0de1905f739559f7231fc2cf4a0f503ad91815f3bba8aae1ea6846730c8d8c3d737c2096ce9a428e1a0e93a7fdf83877ef6b602250f75aed32311c3ee35734e2fa90248937b47103e8ffeb49a9c19d65cdf54646b02118070f46f03639529e2138bfa207ad7e0a1ffb9b4e4089df58b5f14289635996c07c8771d4d1b071822d424961e4f0cfacc5de9c3a536dee1789bf819e18d2f1932404ba47993f74ccf06fdb327c6aa871e5b208e4dcb127155b6a9f51408fd9b0e617bf2cd706b0b80f9099472d7a039656d2a1e9e495c9a7212475251e23ee98f25c87cebadfce6e8298b71395a54e63836087b10c97a89c8c2b572927f513ec750f2c971b816e36003fb2b8930b34f0808af1323a28eaf67801004f550a495731713246d071f91da9dc2daf9a7b0fe88548aa62a1445aa6b7ffc93a5743879b35b23444da2e6149be041794ad7cdacf33485c7bf80702071f643f5c167dac740512a9d16e2e8c83f8c0151319e7f90a632addbd41e42e0127224f18de94abbcc1527f9084059480cd239d2dc33f0044ca50d2ae65da49bf24f776824f0e6be97e540ef065655c42fe069b1d0f56ccfe606147789be950b60cbf7d79197f327d9e6cbcf1e25e81dadd3372686f9c06ea54cbdec32062b7d29872c2b908b5497e48b545b0dc4c7244ae958dde4c794b072c89c3ab1d2ce73697e15341efba8654bef526cef5b8cdb1f107e346ba990c700c696af75654818d4cbc7506f374d8e88d929770af2a36ad96f42514929eef4beb8686305cf22a22c95759c71d4712af0dc2b8a605985bfceccf3ac638095ed5a9578a70aa796d3d1ad58131eab8eb66fb7b875af97f6167958e89d97447808528862a656572284c33480c6f3738010d06dd250088efbd1dce732ae81fe57f7eec91e5dc7e6dbf643313143e674bcb3c1a5db3e1a7c1f063ad89cb5dba7a43dad9dcb65521138e74373f5622a101ae142ef723d36c4d79f1a49155c394a70e7a726643fb8abbcc2762070b126268b39356b38efad7e2a6bd831a6a1a1a8efec26ea6f9e154049a476eeda6cb3023c5aa10f8d526e2e10c172a1acba591bb2356eb1c026df288afcf5342eab321491a2291974b9b32c7514007caa3cd5313de93840b0ecf032fd170ea93991340c4403ddf00b69deb2e2cc161497e4f2e00da33e53aec6238e355e359548d6e82e548487ee664840d71c1ef5fc70c0fff9f856850aeadaa9ac06a54ba656671de111217deb4cc50f68ba5604a50650f4b3378d00829838fc957ebcb2a2bc42c6e2d60d4c3d18fc511b4345a9d6eb28dd75d462e06f4accc2587f41072aab82a9cb14ad6dfb7d30599d33454c5dcfbc9f21502ce2ea05a459dc5bed2813131b8f5c77988dd72fc79218e1854a6084cb61bddfae63fa2ffa8b52fd145310d253648088459f15fd704c4cb7c4c7e97c31791eff10c27218c5ba25c0ce804c043d464bdd607eb16f2e71fd723ad2542bd790a797d0780ae5d610cbebe8e3d4c00ede13167b5dda3ced6d01a098f3973515c9fdcd1a2a717a5953a1dcdf02a05248afb52a20b931dc979bbc13ddd9ffcb5d1e72efb684a6cf5fd6543c7497b99cb4707f3ff0ce6b9b043119d3ab0a5524d797e40c3e756fde25b70efbd5897d58462938c54d8183611ef97594d6b7d8100c388943ebdd98a147e22ccd2a99e08449f3ab4cc440f52a043fd0a909ecdb939ae114d1a328bd23f230f7ea5e23deeeb9c4bdb92416b09f95d84faa68af474d62dfd1443cedc775964f2620f2ab8c069f8f111b3852a17e9e455e95b10efdeb9effaad8bbe2d0062d4ee9485bcc633f9cb8e2afd1a9dcae45d67c4cd5dc39a6f86a1d8a8f3420c3ddc2f7bba4e825f5a7f7ffeac5096280767ca3ed01ba771984235398aac64a4017751ca0aedf5bd582a636fff5fcafe76ff4c4091240bb1bffb2171eca474b47ef3f7ad87334f3c734810d81d66c04ee54cfef97265e6cee457b7987a631c20036dacd95f6451da9d81418bf4ea4dedef2afd7efcdfee6bfeecdf64379b1e39b6f83eb8d0c8e0bea0400ea560216609513530a59c0d29fdc595b3981653722d8d37940db0bb9774a999b66f6a3682d88e0c5e7287385a3f0ff6d3b6091e1c97be2011de621354974a2d9d7636587d0c5d3990e6f2e4caab1267e6eaa8a4195a53b565dec0d2a7542a64266e85cb1c6700b9cf02aa89284a3c97aecf7f8c8f963db0cd8ede12b4ee1e40cd71b2444315bc654a5d218cce8133a5622632ede0ff3b9bbf3a9b7a67d206fe6c7a5d8f219540455a32fcee5dd42a9915277ce9be5e3ed89df03d00a7f56f6161a9f3d0a1c841cd629ca9b640d99924be2e33dc8b30646fede2b35df13da7fdccd1f1e1d8e3e8218f87bf08719965f682ccfe0d91dba572f5ad720c4865b84d3eab53a1db0b7118af154a4c8c6569ca829b079fa5a627d3bb4dfdf81c0c3a350f8305020b47ecc0e5b2f8647a3901549b24f47fd5cc40ffcf5cb9a91e5584413a848b9612e1aaa836c353ce484e2a4387e7ce34224860b0025a4fcfeb4e3d9c584b30bb6c33174417f9f4ca20f7068f2cfe7df0c985818bb54556e0b36090f7013dc45f827ec6e73b50904821c994a27610c51b64020c75cacfa8da02f69e00adbaca3621a97dd796fd5c931cdff0517e5fb59add9fb8909a987c0ef52f4d470996dd85c3ca2e7d9765149b0e789426acc1a55f82e3abd32cec2f8e484d82f8f984b40ad2f16894693efc5b7c4d6f527562b17e692feb18062159f63fe94e79ea9b694906cc8e11adb3a65b6bf72bd6bb40a1375c0ca0916b745a2bfdf1c72b53cd4e3445f601f31b9f87115845c129941be550b65ebeabdeadbe164104a3a69b6aeaa419f54f1578f8fed84c79f2a45a7d6e32a8ab6ecb0d5dbceaa2af554f3f6149be659818c78d63f16dbaf3393acbf5deca42ad83d293727cb43c4b668ec396024ec4548f924bd16a306b8a695d20aab9bc5033cc32dbfb6a906bee5c59405efc1953c7ecad1cb147e0720a5614b2c26ca147bd704caa378b582ac57476c24f8ababe3a1cb169d39a50d9b1562327a22b9fdecc9841954b5c5c49a10fe1931009f2b5b55350510e33dce6a178814ca86732656ebc7cac4b2ab86459b3268470cde912c898f8a895dec3734a78a469ea8abd78f1d4b676dd7921fc294a1d054f598a241edd13e95ea9839776f85898b2e471a76a978a1765b0544a57e2a8d4473f995ca656a7a8367cf8131631973f46adf94f0066740637d5ca6b080ca3b4f6bc1ea3b2311dac6452bd555b7c9ac3e0292c9f70cac73826aeca9f1d513cc28824abe5d5e2fd63f46958dba25ee1d4c251d3d51db6653edc85a95191958e70454e33ab5bf94afe524768a937a70578bdea9bdac7cc7afb0d5bca13e47e643596d3409e3e03b7972a5379e4296f6438a10fd38b12439148d97f86f9359903c5d658e49ae9705187319194e46d8d32ac3db187939086ae8076773a6d0d24a3ff2577f457f9d5cd7baa8a5555af62fd49345d420dafd48062ac1024dded51173cc5e5d61dc24ae196d25f2034cd285fec6e6aa68a348a0a243dbeca9e3dd0f7c7ac82342a7c8784892023522136a56de70a724430e4ea77214948f0226cd75b110c16cc4fb1727237642a98ff6d024e69928b4d68d263109a020d76339d27333a08c274a1dae534c549757a401201d3443ee03536bd9960586a901cb445a4ceeb9e78806dc5ed2ad24045793ee652712562cade01743edb707df96ff78732551ccb30668b7eaee103fc8cc9bc2ae8e054a4f2ed53481e03727e3f641a609fc016c524604eec0ff810692e291942af70c2111786f69291ac85815f669a2ad5a793bdc280c0e396d9ad195c8db6e9fd186b47ff56662538d0a59c2cd4ab65fb7af24b0812e021a48283f8106c65fc1b9398bdba20e47fa821c07b94221cf9c63faf47a261aaf55af50e8e3978ad261750ed030c4b2332539a825bd7381a903a5d4dc6dbc0ee2729dc164568dce1ae1f3b89a10240b5131a0d2bd69e13f64462fe3a6fdfc17639ea840415a8064d78ee6c34e8490e85aba5e101f7e949b579483026f2a5c586ee5647c0aa0379f54398f2f4e602c3c51cb611e103713aa92c0aacfd955b2280dc1be96585485666f79918236c86854109e2ceac80eb86da3faf9dc1d085a66dffa75e7cf7b1e52af89b0e6a8730b6c1b948b56f25cc6ea6c74732440ed2085e5e31df38146810634bde6da1dfaa094b5a50bab7981f9ee7d0f4823dab2592a09f00de149ec79dcbe7a22fc56c1726e094cd4047e42db1231fa203273f2301512385bb376b65bb5c1857a2a81a96463e7d3c75f12441a0a21b569842ef630a3a54507ff632e16bb3054c34b4e761acdf742fab3b1399928e55de96e021b9510124c200a34fbff1450159c20c1fe3369a8609e2265c99bff444f0a4eb4241846bbacf1e8ded26261106f83ca64c9b50a3a573a660a562d93718b407e2cdda2586f316a2644469910144c8864ca4bf855b0330a8442939575f20db96c09cf3ba782ed2bee546d5098d72e0c3b8020a76a269d30e30416a046320bd5274e19084a6d3a2fbf085ce14d0668c9d44e98eb083481aa4b4dd442a3978d01f35b1e1ca68e9ee30a79ea6959ac953abb921c9608e71fc23c24c3489454ec47867246f8e2e37aa226211507f92d0d40b2ae74714ede18556b48495343aed328aebadf3d2e44a03c8077e5ccfc73913afe41cdb5518a467f178912d4be2f554cadfafee771fe23f669e522d1efecd1f551d220bb48eb1549f5dbd8e6228d5d9ef5cc85c4b5b5091ab111e4b64802f15a0f2f694aaf50b48a658f48404f80f9dedd48246e7cac8d8537dd979e81cad97bd4780c9cd6c3ec9017df4159029ab3f576ed83a57e902b77f7de3276dc1195df7ed1f976848d1e46eef19416b57654ac604080f152a20903a6788ea5ee49cf49cb255aad6809fca16edc561966f0023af5298a068dd5b4758d3118ac82e1b5d015d22a7185094db5c3b9eee6d538ac39976403eb600cb420055a7b7060749193fe222a0f20546daa0273ef00c0164b977102690256414a1ea7ab747150a518e1e05a1c7af569e2b63187acbf812b31113ac78c310fe890268fc1e5a1517aa849e0b5e05cc9a51b0b81088d89001d04e2d4af57dd718a81f575719ae0afe929286a8868fc2ee0ad3a240eaaf52bb9c841c6f509756923562c78f76816ea6b5c655e5b0efb9b7cacd5f0892ed8dcb76f2bff7b50003c3b39f3e3a579fc428679435673e7815efdf59735c0242243234236bf37c4a93aea8472fd7c5bd53252ad0f530ad57f265b3b84f39a591da6fe592db6742b1c942845c85dc7e6a858296a309acecb9dc0ef2164b6b8ca805a2e3e06e217341e9f2c327a627662548401ff895f85b6bded91d630e1f31fa3aa154f96d80af575bece692e33edeb6d912303792a450b4229408d33311de9369d0a53f730ed042ff0d315c1c5b23c0be266060947b3866a4d49e5d233f17dd0f158fbd0769f3603d6eb4c27fb7595ba40de27c4f72c435a67d8de56bb732df7cb2e845f9e396bec51c5f39b197f176fa9426e5bb60f624fdddfbc3cbabd8a283ab082c0eb4f168d8bc2920ea26ee279c3f6c32a18a9eeab19603c379590e3f3124c2350308919b8275fc8852e1a6a387b89f487c04496a69e4afdd0a83a5210c3e9d05c4c5dc6f200a5428acee36455c685f8e347ab4000fa093cf6509c542f33328a4b5d47320aff00a33ed210d55b0139d83c6252b4bb803f8aab83d8c3f157bd8c9c05cdbbcf18fe51f42ab116ac65c52c41011f827de0279563639713813ebed5b269cd09531797439489d0e7719df439327086b88f2d8482f175210df36472bcb23d2fd3bfb6667b9b17ab40847a852e0eef0836a2339a88341757ef3a816b57c891fb2db2e1bc6b258ce1ecf0db8cd71fcaf6c80b73543b0ea45cfed282992e2ee891966d6e8ddc0b69ab784fc80f81a049ff5100cfa617894959a208a380bf570b6fe88c6ee110455309bfda5708165719c10e16df67b2f428235ac88cd4c99529b0974e5f4610870d394edfd713658cec7905e7a0b254cb0e05d6af120028318ceaf6c1fe4cd31fbe60ce1b83e9efa2dc25055a906da73daa003045a1a01abce0d8bd1fd7e1d10094c2df9f316ec70bb460aa9492a58e61f821559276f0347f167e80ebaf73456caa0bc4eb3babe68f97b528c85ebfa135828b7d2803c7f45990c5f0228794aa0376cc6ca2e6e0aa5415fe77773dbe1d736567a1eeb6f1ea2313e8832535ec21b973635f9c85fd9f30256027761947f0d1d98d0ce9a4298bf6763dff263ce6b1ac69a853210847112832b7e25564414a9ddd70e0801d65f4baf855a6b3d4b10ed1f5812caf697c9586828d572e767fa2ceaa7ec64d519f8788a8856053db456f06dbc9265aea8b7fcc7ba564191cd3b0232d156213a687178655b66ea34d69e00e85e80797a7b00fed36a478e643c4f920bbfb259b626028b0c0a05436561f8cb29523b135659809864b48fc9852c1a8fae7af02b1445e65f51d2e18aa87d2015f27420bc8f03c271fc70fcc89fc8d69ae480b4501fdb0e2c34eddfc310f07387f2f35c80f10a296735bc6b70726ade9355cb0d7824ba0e225c8158102462ec466f6ee15aaf4f0826116e56fc8e58ac0d93f62a8209ebaf34427725cc85b7e5c5683a2c7d0541433a7b48f9e90c9e93aeb4717f4bd8079758a5e0f8f67f184c40c27af720640ba79769513fe7f189400d75d69243459e4082a32da3f9138bf22051c80d3535f8565f9c1afb64398dc0bcc72e3499576f6995e4aa8c4c27e6f5de94290f759315efab1f9c17dc60773de757f6899be699f931329ee04a36a776088af0b7ac3f2ed6900ab155bf8f428e786b672bf6bd202383936e8436f8aa0706a86ef32235dc23a7815348fef6079fd06534c9494372e2c818b4546fa4738897749edd3f8cd0178d337191b0346fac14401d47fe095048fbb1c48cb47620965eb57867a60f17d201501e32d6ea563bf080e33424af284d0fee82d546d104971c43865fd88cd3f3a0854181af7a2b390013d139453601a2df6f59926cd2ec03605ae4985c740ad1d16ae1fe741344f112e263d036bb6e2b6cf31c46dcb02ab3cc30b64f2da88e3e3a26c2c050dc3183f5d452cb81147453683ee119214d650389d13077eefa80b8d47612ba9d4183b072a60faf6cb6e0ed105d95de5f707857082e281d607e71002861196317527356c034b4f718d71130040864454f8c3af86fcec3a8bc0116f0341f3781a6158db02c62fc8df97631aba07ead233d06232321a4c8354803af67b60ce699615c2d5d5412d26024711e15129842f7f1113fbdd7df55ba1fa3a90e0d0d577b40aa1352d2c73397ad4fc6073b905254caa144cb89ec4f35c36d7f68526a206b8fe80871baa350b991e0480298a3bce9dcd4405402092770b644946cf941d48609f59d2ae9b141c462607682450df3dad0cc93f10760178c23003e254c4d204d9de88cd20d4862ad2327a6d6631c2507e286806aae6bd109976900b01edc97719c17aac207fa698776064a8940fec2b4d87701f2633831e714b2014c0a077abb4b2d225ffc46415779afbd7bcb68f8d6d88958190d4a7a0808a68a64e140f330c9740ed275cb1291a0d35444b7615a72f1168df1f93a6d9985fc50e1ab66f02d263f88ec3fb5d07e114db656c5a73412d9955629fb1072e34a24cc79741abfc043a70bddcf93b186b99cab3947efc7122115540bae86601ee4b7e8c3b4c16d6568ba349aacccb173b312084ed593cbc46d84e9eabc6ef265dccc94bacdee42518e79556e5ced649727ac21f2e8941fc5494fe5089fe431f6448856094581fe2b8707802123a254aebb5a98467705c1f0f934dc9d5ca8dd1045bb78470925f340da7a83bc39e43dc95d54c7ab14e244f8766902818f1ea346549fc548700d035da23ddc5569fae8957c8eb3d272c907c493c8a1768cf6cf87f525050986eadb6842671c6702bdee12b610387c706b01eda3978928a94d333c4d92073fabeddad9b8dec480b8ed06e26bb157a4d0e887076145b64d866378b8a2e205004885ed2840204297700d516fcd8c4530af887834233cc438fac533434dbc6ca954dc9670345ee2ae8243bdb9ffdc99f2efe1889da5cdf140a6a302a6590a90568f85749de91755158867819af5d1b9d8d656f1c5447f526eef9b684c69a17b2dd86c14bc992011d6a505fb4963e2e4b2097377fc6cb490d5ac0a6befd07087c956ce1437fbdf034b48ae2cb453ac5b0390745a8e60d19810fb95f9edd4ef679736ab7cb7d52df93fb69fb63ddbf9475687b3a3aa0502b279d61205514b10e8e3c22067ca4f53a9a52912281d5b42ed50ee745cac33beafd6cad30b1c55bd755b0f6dc4e2869019f0c5ffa5bfd8309d807a0d7c50dd043717cce9bd53ab23780d90ce96b042cb7f667d0d2149a79e7dab478989246a87a5859abd5d909c36f43e3a641936ce6765abdc4822280330eaf382cd6cbce6af39865bd36e8a1760d51e1fc727d98acb861e161ea6934407f561dfd06a4d8d2b151d3ed9231efa004518715cb92976d2a92a0b7629275ddb2adf99864d15631fad7e034ca8985fa1a6c1abac4be1f0d4c979c58aa99d1616e22d1ff401a9229724e3fc115230356373ebd2c0bc3b3fc352ceb3e8d187b37d650f4e9659d8a876ed8efe52e4f45d78778cbc32044b0be8b77ca107abdb712cc83d03dddbb4e906230e497d52a34ba675e4bead00dfff213eecafb95fe4be48d78f89900ca84b4ceebfb0480d4c19b1d98334d68cc56d8f0c08c0db5c3dea33de9c73107cdd8e0522a3781856eef151186a4ac4ac76bcc16c51e6c20d0298106d97bb3bc957f20524e6ba317e7bc10e9f1877c33d2ff75caf4c3538b008358f7ebfa1f5ca7ba176958ddd92dd7223957a561063448885f2cf25401b8ab8df33d12df0ba55d988ae8be7b5956a839b938586c0335ee66cee64281ee04dda92c260ca6770a31efff1aa842009d6469068b73053225f3da13deb3ced653635fbb26cb077c32611cf320040879f50e02b4e40140f68563a8dddea80bd17f51e3dfc11d56f5b152f457548f1b833b78758b231a09115316cee0efe4edba11482be86b8886d9bda6f079827c6941817a761c801974d5d0dfae50cc8a6a538c9363c624f1426edaf8428fdea0b056b510d358a83d9fbde6ac0bccf84d8bbbe74a4d10def9d3050237a42ad62775a0decc28ed3f57c90b446bc4a8e4bb88d096ec271080484b762516a59178c8908ceb3c5664f166be0e073c3cadc2dfa38521828523ad8915d8416c4a4bbce794844755531faf5cadf449b636085f0f5917bd616bd36b52d38a869691e280035356b909957aa1589b1bc86fa937244bd46d14ad9e92b788c46fa38f954e813d79500ecdb4fee947194367a482f338402e4b3faaae0d6f803c240e08473def478a2cd16029ae0cebc2e5e4e2819aef178f9dd601c8351bcdfcc0d2d077480c8df13d20fbc6ae202805a7e106204c45044bbe481c7f28faded83630e432b5821e83c644583f195ed8f1edab4a1f1a0e183ccd7e832c5e6793dba84e6d0b98410bfc1169079eecf04c7a58a9b9f26929e990b7fbbb46bcd39d90e61853cdd01c9799d2caec85595f5959afcedf140e46da5042a2fb5f2b7946bb51cd5292f95ed13027efaccd753092c5896ee3deaa85b3546b6346dae5d702f286c3888c8369266460638824db4dbb7f62ccf8fad5b1d1d37afd114fa815458f77f68d1b2d8a84df5642655c6860e883992c18f77b22aa1f56e7fdc4e7d4b2195273f825a3cfb8a0c06219015a6eec26983b8bc17ec0a9294f4cd7a79aa16d59464960f4a57bcf2a2cc8cebc9b11be172bf78d8de2e6f7fdc5c059984c8813c103b11b8a12e60a6df07d5d0955f0fd0c0aca270ee3472926f566f07ea4023f1a76a15828f2f7154b7030102ef35d10770ef5f35a523361129e0ee7a225f70b7fef601dbbc72829b48bf0a40f564c02f07c1efc1baeda74b95c0a6f1176a8e5a7e21d61a7ff9bc23b360ab72ec7c70c9864570050f30193de52c63078a351b603181f8efb75648be3cec4f85dadf200b33743b4c5429fb1ab51169bdd521cb762e63c2a30f7aaa77ab7bc15eb3579d0efbb8493e68c954efa966807a4914e6b3f698b0031f07371f47dc21c857633152957846232a5f2bfd9d6c44b441b74d92c310c19469253800c362444093d06b3e5e00b193d806b6cee20903da379eebe446e19de7f846d79b089a893039f084b49f6504d393a091978e12e37a12637c97ecbd992d2d7899345b91f4ac413cde22e51d539a1323546bea32f9755ff9b7581a4ccab34bb649e0446cdfd178abc0803887eefe3ffa71697604c2a9426bf61a94f663bf4bef5883b8bc14dc6eda673de51ac04acdb29d5a5535f9d74a6d2bf86d79f3d1f78feffb9045494126a9717245e2a527ea598ddc217e42f439f9527fa2342384584a4f818c53f4365a2ccfa6c6b32e78d994cae8598fd4e06e917c94c87fe54813b17028eec8341d9e734a2c6b8f378ae1418a8b6e82f841ea6d89b8c5f5b31ba9f7f9c32c74ea845e6279db3cd4b56a7b8eac54de2ae20c2ffcb63c1e75413946490357973d6eb985d1cdd8683c8b230d7d98d99c30eff4d45537b2cb896e9709a94059c1ffc1dfd5d4bb704aa0cfd1c830ad1f196823943f627b5e365f65d6a5de073e50dda934cdfb184ad1c656f35142d320cc7d089550149acc944f0453e4a9bb0e840036d6516288c1461fcbcecde789af83540bf9e7ebf8a20cc51879e9c41b8c59b275b7cde0aaf83974888d6be12063fff5de9ead265adc1d61979a01d076465ad1861b4768144d007ff4d1230b62dac34cc95b538d7c4e2635ad88dce6f4f9dcd02abaaaaf5db453922b2b034d14ac4ed3150f964989d78355826ceef4c39da3b62374f7030ad889c040649df169303adb748dd7c4c418f2a4fbd0e9ddc63fa61799ab86bae0d3dc9fbb2c8983ec31e48bfdd1aa3bb0ccb72899dca2be1c203d25f187590bfa55d58d1168690e7794257096105e7da7e1a8c716199b534807ce001f08f1a5066644965e4959d5f8653c34ee2355f471b828397538b066f0077512bd8faff085d58ab43a14ecaa5c3aefc05d5daac6504e248febbb6cb27832e33dd575fa237478fb44e73fd64f11b3973f15659ad7477a714a4350df49654b3d05f4f9cdbee8af3d55684449798bb97a27b98ff1a1045b89331691de45ae4cbf57bbc07a1752df6d1d4b8daa56dc0384d0a4fa697c2c60fafe44f2c518fd83fac81ddaf2cf8412fecb5bebdb28b6b34085fd42c3aee265f2b4d04bb53a665342914d9081308f00870224142a9c42ecefc873de184243d59dddbdd31f3c5c98dd1a69840c938b6b080ca175f76c63a82b0ae3ca0ca95d4f9456d8d03af776ed5f719bb140a6e761839265711a99d5db64247e8bded60dd2bf3c1f86c1e11a7ed7b0617b1a284afe471973aa0a93102f322c80c82294b174457d2dd28a2fb30b2006ba7355a59b57f3812c2a0c0a6faac7685336181dca29e3b4f71baacb0efc385d101770114511d9f1e731096d65df32756521e9bf7444cc45b160380232846b2ebd7f520fcf7503bc328a4ee165ddcaadb6acb06676621045f0c63eca8d36f3fa0969d324c5c1e1de0b943abf6c9b8aa2213c96e8d4806f17013efff67411d43e1a71179634362ce381320dc17f79fff5750e47d51670efe44339f8004be7a969c2fea2e3c5433d44c9e0a967e4bbad32b852ef20e5b4e6411afed6f5d06369beb935c3bf896c5bd2b0fe479306ee8f693541b6037f230e202078e192fd4a483b03cda8705ec96f56fbf3dfffce36c6a3f8f49d700c4703ab64a069ed610bffd28441c4239620b33e284cf15e6810c09db65c82b5ae26efcff9f332c7b76004e79566fc7b791f20d115d2928dd6c9fbd063bf9fb7b37ab43eb5aba45f6d0685b240a580e8d376f06c381e94c4f84d2a34792dbd1c399c6f8188975ece66c435b090e36837534399c1514b47aa2985b35d554df681267fc18bf3613dfbb99bea0e5586a46f526ba90116ba94000864718859257600e3dee827c9d30fc4446051cef8eaad1371502803c71209741768884193bda7eb9fba46af2ef08c1fc3c997aa8afd7a0ff98089d3ff00b68082d42938f0356bf0877ac91e64f9398b6a56a3e00a6ff07990adb9b784db4ebc90e1541adeb3d563be612e362248fb87e1e1d84949b0b7faa1588dc22d1ea911bc04e1dcc9f99e6ab83086e6badad288d9b97506bfdff4fe48619937f76873694145ca65af2964d38c14c8f3a777db482e4ff703a10a2edac4f74cee042debb1d1c00af464cf18b59e95d4ad26058e8a4cfbfbc5575fde13156b8e56c4d702251c096a95c50bb3dd01bcdea4d18a65a7b003cde36a213c649f9b37037dc08b07d65da1cd5486974867cc5b3a3a668d9a34eeab7f8bb74df75e53c8216414d1bcea2f3ff0c2b16bc0c219e9e146caf6aa06baf33169e1610b7d4e351cfa7bc5af6df1944efcbaaf624926e53b66601dfea26d00b0d15a0a8ef3f24ec63fb4c8bd369070d718d82d0cda09194afa3947b434388c48ba5543f50f3fb270ab83fe7b4d8048db736cb5d0732a0cca13f2360f3933f28441a4796cd8dd6b0eea03e77adb501242fa25e07cdef854b0e7d5651b830b74b1a75e8155794c31bc5d290c19dd44b18d671eb290b387e257c7315f4d8b46a3c0a87339502ab1819dda9aa9132d45ef00e78f0e957e59a7573f898fb94a36f8903556e88cd460a7c5674f2ea434ddc1d3fc519dbb67241e7439c592dda99a11694e1a7f19b8871ef6608c148bb54a878a23928f36fcf910ea0a4cee08349a046737db72591198547fbec06057c175b59c6b76b99c472a2ed52afa6431908fe6c81e51b97830e6e247123605338ec937025c08ff6c313d81037b21e82dbb3156703edc4f2491757db34b47a97c0f5fa78035efc6680bed550715751ca35815b17bc12c50a2fb5c7eb300de453a38eddd50d30ae1da77d1191923525b8ca28a3a2714139a4765f285c831f447460b2b6d3c38aee3567adc0003334ea1fb1ecc80f5f71266319d4bf612e71f4daba8c47edce5c88a33b6a77bc30677a50ba48ade925445dcd09a32b14e5789fe967fc614d8c9d5c7aa84b0090a64c45b553476f45ad37183bf94a395115b010567572975d56ace270007a218351fdd7b85762e6be87523596bebeeb65c7be1293c973c27824e751323c6ab2b382d8a7c54a72ab4ab090482ea133287cb76069014903efcbd55ac220288b6f8b1a08c6a99b329b3bb4edd791a98d3b0e63681fad14132f089fe34ec668044e0803cf06535c044e42f48c8b9b7ba3070f89de8310df3131a98bf9fed457279cd81e4f3852d3838c203e438ae60bba15d5a7e57da7dc42c3afd0e45296cd4b9ad3e110f47957f4e7e1703b8ed00c4350f1ea1094a83fcef3e43e3f4e3b49067d66b0bebc4721d7a61d6e1a0888629110abab2d15810c346dbe4f9a32ddb55791bac674d6572c473554dc8689957d4cd7cbe4f54016560d73415f30bd86cf19e23fe99d1d990c568f99ce25d5f00a33d10e6166afa5520afeea8098b34c86ca9d7b0bad45fb09e606ecf1041940abcba3a50e5c519ab6c9c93ad8759c9113c951c4671d09085a0f04f1dea5770cfefb8bb54c4822a86dd95d10ae02924ac5d2d92dc5a7d2c6b10b5943c71d61a0bb5ec05de2466afa15c02e7740da8c5295713a923608eae3d63202fe4303d3f1a5354efe720673b1a9846f3fbfd4cbd1d2664c4703b57e483f1252547d7a1638501e19a6f233a51e6c4c01dad3825f7d46357d9c3a7b2af04a8ba25c29c57f24fe0426aab8c7062ef2afc1da91111561f3c28840c87494d1441088d8f2b82014837f1bc57b8681f0d69dff76ca2f3a03a63c8472d6fc312c4a96f2d6680913bad1fd834a30bd7205c9e88f87dffe95ac3fdefb2a34d8ef72895f78004f123b595bcc6a482337b2da51d015cd0d9265d27291866cc360b8a213eac4a71f95868f131ead3be6cc45235509485a9f64f8d20323170c7553b6b0f129927267bd719fe265ca93dee3367bb644d9d1dea2c1c4acbe0d62bbb94b4cc2d2d435dba61a2a6465b0855424bc7d4a86da0abcdeadc8094e45d3e9a3767decb53701d085c9393fd20365a242a32ebfea0c01c28595a9abbaa65e3493233ffd599bc15937bffa9c0d2511e9e94b1b912162d02855fd8af9e5356bb80a0ad39a896aa232e99e9126c2f024913d5bb1cd3d363afce34a18396d51586ab9cb2f780578f445ba6f23b40bc1293704c58baed4918660e582599f0dfa1fa61bdd51fc68f4c91fe6f500104781f60cb8eca54265e6428a17aae212690b4e16d44340358a2db15e60dab3950b6b9b76ecaea2226a651d683035269656326c2287b4de5757e6608a2ecc381b6efbee5d65de8ec50526b70f7d82333d13fce525133f03d1d43f830aae047b7952257fed9004f54b8187abebbb544a65671b40c374dafab463c6481a26c11b4269072f3283a8aa6b59062e5127d84a230f3092ca632859eb952917a72951c97e52fb22aad8ba1521ae4f0c1272b1d24c7b44a29ee574d242ff97d869d1e19fe112022614f114be6087c020edf545b2f4dcbfe42fc6de34686b26337ff6971a23d57bc9680a76821f1969229e170122778eb401a2195cf976a15fe4c07319b0afeedaeffd7ed93e70914cc5d321f22fcc0e2c48497b78400dcd398671f34f20cb75b4290e642a0e6820b00a97cda989dd657ce5aab55ada13926e63dc59c0539d8c92e6c43561196113421aa1a5250b303e0f62789be75ef4562cf0c1be326f7f9f27fc7ce6065683ea3d4c7dc88424d83925f6bb4f73bb1ea84f532889e739b4ba67384983feb3405056535f9c59ae4d72db53db8a0ee01ec2cac203560d8712d533e09be917dccafecb2ab568e7b0c919fea1a91ad73b79dafc8c7885c704aaa1e38b7d7617b86bdbc027af569f846e1ec69f567c3a24b921110078265e18da3375b6501b6616186064baf3da052c65eaabd54b3f7766abf3a96463589d3539c0b7abb85cbe7df47069e3329ae202604ddab8081864bc3d30c3b80620f4a79bfaf80aa5d5631368e70121a5063164d7287aa7c3b41e2fdf60fce88eb457fa21fdd8774d641b6451b253e9dad8c4cd374579f52f0222a2b108fc8a92fe3f65b7fe0668fe38b61091947684f136dacaa560ee17b9d84e1519ec4f6cafc19a0770e574438b2758bdeaad5b15ca2d45da420b1c9faaf92b81124e911ccf4793f1a1aeceeaa120bebf70833f50b39cdaf8250c1ceea15fcf4ac31ff38431bfb16fe4ea0b032251cc93c9ffadc25e74c234be8accebabc8913d69a650334723e8ff84a27ad643d678256ff743bdafc6e1968632654b2709514266d1f8ae5738c5b80fcc9ff2760c5e564b212020afc86c21681bf2bc76e773a56b4690ec579c48968e2b6c1237604c02e108022a0b969bf8b322bfa7490b22f683c3e2b12dedfa97758b5592c19ed5118ae954b75531c9ed9490811a967bb3c73098bd9e7245e0c1a8c229827ac5ccec06a73db538312ebb040d0ad6c7cfd17eb3d684b4f6bf1ef46ea160ad029d9e3791b000b003910b8607e8d0cda91072a4e0a430117e2c9bf590d2915f9844d76cd58010decb5ac692ff75dc31e860ce179c1eb667113d7bd835caa63fb631819cdffaf125767cf0d350a5c211d82cc49f54ea268ec1de773f1d533760da191c28aa8a16cb9861d02f8c90baef8e016f1ae30071b5972da8d1b452079b9dc1ed2da6410e30c21dbe0f4e8fc0449e4fd56486dff6d3f9f6a89bff2a4e1a22a9a4e0174df3df3f6e52f8d774a042b98f9f5816fdac0ef6b2e3944cb9e77681582a95bb42086387b001c9c862c2dbab6d08c8b9be63483efaa498f8f9b0be161949c2f1e1a7b13d328c673368a9cf68c49b7698fd26ab5a8292c2582aaf123190d66b1502aac8f9ef3c0809d8730c69fa656b88f001492e53cf7b81744c1b8e63a49cea50123b44371d435e508e8bd80bafc42d1813525a460fcd866e98db6a0ebccc5b285d245edf06a72307c16d320e7bfdbdc2ed44416b9dc49f89cd86fa8a7f72d65e006c1d28b0b77db7642de1dfa1b530ad3cbef62b385cbc1c88e1a3ba71425120dec7d8f2ec2c4930dbb92253e1b9ab0d760f70a313695eb3dabb9d4718d398a37e568d5ed6a3b95a3f0b7f4a66b55c0dc286b3f64776785c859c5c55b1537a8751d371999c895eff104e212dea745d4d7094153c5f09aaa3c235fe9ee49f36f37f2e276fa9516b16b12b23e7155ba057661137144c128c67dd86c8963ba090201fc81661be477cbf3d0892179b3012150ab27c72291ad87dd0601ebb59b934385d29634e07f582ca0fcdad424d0ca091837f22caa2454a84d0e0dcb31192a1405106221cc4704ae197357db9172add3e010cf4dec0df047555a7d6d0901b4f26c9d7dc2d1f7041730bf2ed5a2783279c0b99b6a14ac774fd77a6d2a987c3869bb8ad1fedb24b632ef85d611438a24cea1327106ed851fcbce29481e8210796bb48016afffc43ba00c004ba6b60a3935e6ae5bebfe32180702d1dd633de637fcdad74680e9b6ff3e247c8176155df7ed1e69fd5d8c21925783dec7e3f984281321d8511bff344cca0a3b039a367fb5b0d98946d82d50ea248fa5894807034229ca31aa5ded468c3b273c8d21f4f615c7280096c9c09350f1cec072de3d711ad72c1f8d3e3ffd75ad038b69e2febf86991f6011cf31c827393eb04ef38e3a3e119da0e0c75f7478d07029f339fdb478e280a1859c1ca2cd690a6db35226d63e4718d37fe4219f96c31e32c60b966f51b597737bc555eddf6bf0dac567b533fa1bcbd5aa14d2d276fa7afd0054c047e163766437418b1db874332be201508e89404499fd6b25f3a4a7e9a538641f4c94e998f2a9d96fce744913aa951956eaaafd5920012ac1f800db1246cc5b2f9c83e3dec87844d805f0e2a70f005266357a17435381e88d1e12a620b04a4009a96763e9655aa9e155ceda9b66bb231fbee17a4a64034934caab239cef3a7993aa8d24529ccb9f56c60c3289330e6409109f7fb15ad0c310c851ab8bd156b4ad88bbceaf320df96d94274821ff39e51127a276e856f7fe5a6de92413942f50d99b5ee8cb45c88d52dbb3d428f123b186a74986306e255578609ec739f033efcc962e9894f7484316fa286df560c623d1969a57108e067c8ea1faf48a01981cb07965a46f33a48fc85538cb7150196222737f80bdecadb6c156701ba06456c1ff3347236fdc2cc8f23f1f640e807f010e442c30af01cd45497f206e0837510e5f30b12432843bbb350727e405ebd30f565f7202a4722870a5ac34d02d952176179682783231c093cdf277575a4444632807d5256d36091651c528fae7b07b670c7503cfaffdd4bef14d02410d57112c3cf608e73adccd8f62468110b335ed3ef8fc825fadcaca8e914d4e41b574452d1474ea36c8919012d52d65f3bf074000f830ac3e5b1b62c4c63faf2a2068682d4ff242e100ce1b26379ae1622d806c314091b8f4d5550d2e4665905e841bcec4131a0041857c5a319944b49f5683e08bd4cf6958068de99b6d7d64e2fcb43a11a2d2dafa4daf4db1b84a18fbec67c745b672a046d071f20520a27f80400dd7cbef73d78c871444cb2b28545bb7bfba9c302d1b3532deb0eebeaf670828ced338961255dc126289ed2d5b971c5d5bba1a1965e4a0ab8c702848b72447a4cc4fbcaa3a72f81d0b4bf26e80d558abbc04c5dc9fd1c7afe5674daf8725ec7ca798c4b8eb1ba1daeba4eab327f0aaf1427a9842d93b1b97aef12a9e7c1d4b02c5b43254a296c095b3eccb72c13ce3a63e25908296dc0f218cc2496003c7d3c8464459964c86ac5a07d7a3211a3cf78677dd0fe9f2d1640bbd15b921715bef20495f70ec9c923113b44d371c71c5fb10daea8c43b3569de896df111a79311372df3766924338203a1b296c85a20f12487cba596298f42c5aa966251161ea9752aa726663023751b01688e19fe9890ec615498e5ad8c8ec52936ec5127ceea00ad14be37992dd259bd2ad497e1015a9d8cbaddb97280e5198b7446a1a4ec41ce218d069ad46ad3a4ed82659ebb0cfc903afd3ef0a44e07ad1c5e37d1d1d9e020f2617510ad91fdf9e233c1601c68b25ceb1c205835d63a7ae2b855350f6304b95c5b5797182d9a9bb5b9f7a3d5bbcce0a183f2e15e783051b91f2bf5a2dbd0ab53f6df86d2988586c33deef07d4249d3c06a38c7f8af02e1317c933f1c1ec319293c22f14e497fb4e53361709213f9cbd6bba65254c934822a713c7ca34f34d2bc03a500546e42b28f6209d7800bd4d6f943e1da184a3a1c78c203f3be02bc4f48b49fa04b52846d66f009d6e60793340a4163c13a151f7525843ac4288bc4e8863296f5c667325cbceedeeac4ffb80d030f49ba7c5d4549edb78ac614e45e2129171b0e8f38f4d1cf4d3b57b53edc587bd40a42738f64f559bd158116604fc01428a7f054bcba1cafa12a0e79d648785f168d051787e32fbf22513db92ae15be65bd10cde664bb375307e6edeba12795acbd752da68f758065a7cb938511c47e9d7e028cc3a3f55c820abc95e12950e97eba8b41a3416e6437d0c7c35cd60f52712e788842f4866159596f81b10f89cba085bb7d006bcb71e23306753c0e47fa01f1e561e00a5bd14f82e2b2baf3403df749b8531f61470f4f432825d96c3598670db12828a2d5db695c63a47f420a04a07690ff8a9257687320b6b13ded6b07196a375f7d835b856ac26a7ad3dc71504767db882528838677c07615da721ee9ab45e953f698bf2c52c1d2c479948f9b1591048738d563f6d1593d314a62c36db348eba39007ae91b39ded8c84bd168a2a5d15f7857e9c734fb76dd52e07a8cfd59317bcb2af0c52bf2818a6561d189a04afe1e145ffdc06c6299d89b56cfbec614372d494ef8ab095205c09a7460f17c48495242d18afaa5b0d06e14033fb78594c051378a7d12ec02ca260cbc60cc2b05e4e514c9e8f52f8aef6bf452015ba48eaede0b466b58a0b939105ff58fc325a33a07ee7d0706fdd022b6156c0d58447755632610f33b3319209b65427118fb2b29f96967fce2abe4d0a086a5caf1d113cf4139f74abeec457764413280c66f4e9c60753f6e75ff6c9b15c5023753e73360ff4c3a59898b2109be4e88005089e814a603b0df99cfa47488425592ca5657170834d98cadb8c262e48129aac8fbb4c062564da8f1f34cb86477d756c2ece19c2309e96540b9252f7ec95e6821138d2858816dbf4f922764ff8827dbb9e5d6080b83ab0a09e282557e6db43b0ee9d4b8258dff8e205e720dcf42e848ba4f007b474429ff118aff83d66705287522675ae42e3f1419c47904e168ea4394e906015d582c86ce854f326e4bc0eae4003c5f7f5e54a7fbcbb3a06e080f5a310cd75ccca16eb1512e7335722e217a4b106a986ef60323a757e75c816b4f31ab1a4e2af356b719c0be72c930c9cea52a409b018eedca933f3d85888e71860e0416e910580e72d18e39a26531c4ee03c864b19df1da41f88a6bcb45ca6d82ca3c393a0edc7de9d0000d7ba82f6b6bcea2d930d89eb574bb5a49211597ed75dd297ae5b9dd60df90365b3ac95a21d50631367b603f3d43e661ce70e6b179afec0d9db337e10b5b9dfd2892b1230750f86a651a7113aed7d5ac02e1fe459345c2cc74d4bfafef3322564fe51133862b573f8ac91162ed8806bd79f9eb9557d8db5f41195f522f5ad119b08a809db818506c152eedcb8e0b0867016a9d407f8e44ed80fcb34303ec674743e1672706d567c78694cff382f7105f0b426f8d0954f5a19eaa8fb58c6188eb082ba57f7ca14e089c02a1c8564e40e7d17a0e0de61cbccee634cb87209879e60c69149a981e8aff1d6a133d240592cb34e520b78118a419a21da26c45db52729848457b094dff8c0fc469761fdf6cc1e9033afb6b45600622a098d727bc9f8b049d2bfac2baa5de7e9e1feb25f4dd434c6938b7b10b13fa4e9ddb42ba973ca142e0b0ae9f1799740f376b042764e96856106bbd02c88cf89af086e175dacc3a49cec6c95b1e53d94f2f6314b26dba2ab6baabf6d682086da7ab69001cc7f82f2dcf169e3e8e2b90c28b40f44cdd7b9e9af68d7b4cfdaa08950b51795cd969434eb24a6ba33fa3696da01631214aacf2edc8587174a9d424fac8d012965a16a954f31ada5161449c153a8f127c4ae59fb2b94ead26fef6a66a9122b22f1c863e6296667e34d33c4ef1022ce34e054ca9cd00a423d95911afe57b01848a1287eecdb0766b32e695ef765940ad7ed6358bf7a279beae4e0dd155ad9a31009219dcc56001f677686ca934f518d04a2b7735f69ea13d22eeab33789b17ce940476bb047afacd8e9ca31ad30c4ded917912860cca9eaa8442632fa29e3c7248135d43efa62b3602e0b8be6478e01c9a64536dc19acaaa1b4a19bfd0c3875e2cecd4dcc5b7c7c60e71e11b34c2582ac7f51387d02ebe27751819a2156f48925dd4bd7fc5d7446812cf849a3d6c72df7fd6c3457f4d95ae456d4915b9ed9c787a280df9b1a5aa7d8d72a7665e2ec6d3262dc40976ff9adca0c01c33ee03be17377e8027491f54e4f1a76d1be54771056443e40b2829818267d1189f854375ea4ace01645bbf656a454589bab0c009f7f3e4c6a6f0d30d4e3c32b1eda056564822419fe4613a946840e70c4b323fea4f1a023fccf925207810acf6dddb538e15c7cdf66490b0440c198494fcd2c31aa23f8a889c0190c0b3f233369d720d24a13564803e6778cf010549bbe64c55af8af681e6ad7ebebea7506dd418bc0f5181abeebf08cce0b707d129919993f0d11d26b0a1eebec7edafc4169f9260941c995fb5e4f5c38bc5d48590d5b49507708c34f02f554189647655bc49f53adb3617dc355526a35bf1da04f1a457e8da2e5310d98c20cde6fa03b880a263630f2d3ce5dc57dccc2ab0108815f0b6cae2f4fcad3b062ed18bff3cf0172764e6c784fb707f7e4107f77b40af63cd1eb875ed255bc9e46f2cee1b6c64f33809b1efaa4d7e63b22f01d078a3c2f7f120de7e1564c22962cb0b697392c7bf0e06fc5bbfbaa4bc4d7e693b9a2d34955e9306c1167dcf9aa665338e88103d8385462fb0a6fb4605813a4bac8c09f1a8e0ae02df114a7eeed067c91b98a39dde01e455bee3e555b3a806df3688a79c41c18ee5af6baa0b618413e39049bb187df22e325af4e63a90152c01cb230dfec0a1708ca06a50cdeadbb5975a8a6ad910823d19e8a0f7bfb575a82ebb902b72a6532c4a50c5a2e0029e8cb8874c3601f7dd56d1bf8f3ef10cca1934ea511c962da3d34dfc6694a1aea41d8fad33ec0ebf79c6d63ca422b99d19c0354218fb8395ef27e9e0e140804ffb2ea063440cc4b4b8a85b676118f8f43d938c596b900d850175b16ef88128baa42f73819a42cb9d8e83fe949874a877c42dba057b3a25e7ab8cde89304e406c5048de87422f035cf826398c823cb18ddcaf9dfb85dca5d6396dec66c6408782b779f0a64dc02cc550d5d5d9d026978a9b716c95337486c7f4dea2cc7182841a2dee157026192a00386082eef03abd90c0825c7a7932a9286e8e1678d1b063390a0adfc2dbf4e8f3b4d70d5aadedb63a9a613ee770e66bc4f67f13a934354d2708fc3b8798eee3c4373911c03b285b20512c31f3d419ef7172fba6bd7c768dd8383edd7d3b3dc43e8ccc1bae058306d745eefa2bf69cc2176137af14b5b4d5f061f2c2f62852e8e93769995895165636f2f722ba1f71fe502dab212c8edde39420250c9bdbb5443c3158965e7ebe3cf7580160c00439d9a830a2e1d67f39a6a42b7854d8e84d5dcdc4d279c5929f6ee7544a13ab33ffe6c2ac0d0cd352f2fa86e4037b992d3d58f07692c3c10b4c29035221f07bd7ca6572c05dee1236c781289d2938a912b43f6f6d5be3bec92f6d6a1bcab87ad3565d5fc7e680edc6a82b5cfae1b99b3fe6575bac9f25e8e7866e77cf01d69c4bd77f5c2fd7216165f7653f98c724e414ae426751a98bb48625631835a8301f979661921b69748e8cae68a428150abd40b056003863d094423e15024b899e1928653c7a5546372b5206114affeda26fa77ae2c1e15adcb0c71339ae4687aef44df6451f67a1a0de3c88880ef24888a9c800848c4f8b25130fbab756472cbf479bb65190c5cc25243f0e301a2c860d3edbdd94f0c08ae10e00a8120baaefda936092622e06252112b8c69e953cfd55671ab4b64c2bb2a9de49e391aabf0f39607efa908ccf46ee812794948f1e4412d83a5b73218ab82d21c75a9e1f2c4347ace7e6a1fa0ca8ecea6c8858d5a8f73e3bc2ed93127189f2b791c41d0ddcd6574102d4077bd5030804bf7cd9ae5e4fc8943f980c3f3567d14ed7ca1b64c1282bc378f21610a4644ecefae6014e0680b2fc030c37489e3282ea92e38d678c229d00dc1fb604b0b1e7d66e37b7d120efec03298bd636611b6a537a902a7ede8a476689c886d619eda3d2975b934cb73442791251fa608d688eb7f72d4b38fc2d9129411a20f519c8ff9a31b39b20e42892f2ac7e9c560213745a1d1e430da85490df21661e6a4ecbeda3c8aee19068c4d40b56d5180465cdb6a09372d24ac927d5d5d8e54301c650035e4a1b967e114b63e87d51b107049eb8e39dd53ece45df43b2de07e66a676786fbe61c2038d2fbfd29e3ff7532898200ac2eaec1279e2e5b570aa9fafed8cf1f8884fa1752e06bd90f016c2cfaa0c59f4471e9786758a0192492004e279630c71e747913e1eb1f8878e014fc2bb372b31bb4d57eb2548bd49077a98eeb386eb731d130bd93fc4bc5987369b2ea977c821bcf4a3a842d62c634fac5b972903140f738304504db08c3ae63a3b5f896c125c6e2184f4c6beaf32dd6378370d86950d40fc7dd7ce70c9dac8f020b207299ea000ce96a140be61c30c222b330a08307aee9a8b52be06964b0e8bba7db255b02073809530b3ce794ca81bb5a12f06bcf0a86d703a0f31576982164c6195e3ef7d3877c849d0200c0144167471a961fcd026a0d2f452d658652b278ed075c9328d323580ba8450786f5de46e61553bbf2744639805c0a12956242d6cc6e8193aebbae909c606ce56ae95113d0bad62f91a4d8a3b153a97df6ad45c3651c8c0014cf7680e9e3e32776fe7691ae5cc5891391f087d96f461405b1a00c29ed35d4b4f4f69ab860005dcc30da6a232aebd49650bdfc30a0819daf4f29eaa628183a75531c9ef2d5d04ca40cf523fad41adae707b0cf2d31e1c9dfd5cec2032d13dbabd825847c0ed523f2159a30761bf0a6e6aff37f9e92679e81ef2ed92bc2e98e0acf8bcf854989f2eefdeacde24c3cb7aad51898dd128c5390fbfd18d3e9dc8ba045cfed9a4760334aa7189be034a5f68c8a44676299ab651afb16b82c5722f7dba422779f5ab136451985013426cac99b97c706417b9aa12541a6081ff82da21835fe5b3e36f2c3bcf950140705e4b45b4e27dd7b400d9407d8ea85229cdca8f5a3d62817ff7845c3e2567f1433f775af25fdb61b13ee59cf7a4f91cdab714789f2269357f4a6e05698c40633168fed1e67e04566dce7a8b542652b23844894072c694a8ec8714bdd1c7fcbf87c557cc82bbab75ba539677688c8931e8c3d9b6b28c623fe80f4b2736168fd1c7837e8b188eb190cad146e1a9f31ece216865251a7ee3c5103596ae60062f3263ef2960f83544df2541e1b240a7ce37ef79feabcbbac5b2ef9928a96f5c8728edf091b2916842025956580118f9433234ed8e47c9d7f06f88f577c23a4b57d05bf4d6619d12ecd1ee50842fa3001722c4fd7014620963d33ed2afce9c0037ea6ae2285326a41d36d576d0da686dfedb440f0baa59bee77e68253a89394bb5ceb0fc2c0ce9e6d748f86c68aa2573a2fa0b9a1aed482f91d704c543d2469522671354a5d47454971c9f04317971c521b29e5503af09bcf3e68c4e6894b2d3ea4c695e7536cb18d9ff74ef6139366ecb0fc2e07056ffdf316c112f921419bcb5aeb3907b6ef887614244cb53653911454e83d17a982b5e57a511ebe86810dad389d8cb42c56dc6e098105455d7fcd528d047688f6cc88b0b9d151285b0fd75e274553101c05cc828ce2698944b5d7daa851d0c5c326c4920f90c7ba623ae58fa8bcecd44ad39082c81e8b957596c4df198e277086fc7cd37df8d8530618947b085595e9186fd407024fee4d06b73365e40fd18d46fe76b630241c4c7325b0fceff7fb09fdb90e624c803c0b95b0825e12f172f48e03b81c20becf9b28c07d22cc050f7fcaf377f8a7899dc7c126e4a96311027cec45b5ec9363414a53c71e34e9f09a0c18bd3c93a221afb1a51a993bf804180c04d340dc17c0b97b1ced318d1c88c74cbe4f8a2f610c9b55477da3c945f312a35230acf42f7c245c861302c2e94945f31ee21706c27fb457ba243cf5389dbc338307931b370849ba9a85aadc7d8c5c19038a6b667e5d3012bad21c8a66ec0b81e119bc54f957e3e5351d340db2b7d4d8f32b4cdabba71112624ad4a3db47c2db73a41b227bb7904b10711d1707c78dd7f53e6960661bcc3f8aedfc9a0ca8bb29b0c91acbb569f3a7595a2f2d2dd9524a99920cfb078e07b4073f56cc9ffaeebd7f5f317fbef76f1bcc1ff0fd1b8bf9b37afff6327f6adebf6f307f6cdebf71307f58efdf39983f37ef1e88a550f6e03b2c6980e806543920efbf10e87b0f087ca0d5d78440350f64f3ac108885816e643c0867be43d54d3867ae93d55f59fd21f53499d6a47d52300e58a669665454d49ab4580706cbfcf6a5167b61cef2d6e584410488c203114b04a48c179b37711ecd3b824b2da659719e7bbfe9778b251559fdfe5a960ae4d09e7e0e2a9655ac537c745ff291e94b82abd8b3261fbd9b82b82c56c0a468727142caf8ed09dc7dd9495a3e2236daa13771d80b94827a7682274d64cecb088376964819efad60f812ad99f1fedeb4f325f67cc0a4eb3c095fe2fa3a7fc33d9d9045df849f240921095fc22740e74d106998f13a5f8248c3921e335ee769d879127e925e6a864df8127e4baa91b5186df997309b3f5b7613c45942e84cb405dbf919afc5684f09efdbca134b5c42d8c41de6535c36231c610398457fe785481615817accf81d31688638690b0193dc29ff27a9f3e5ce0c7109fd4992104e724638495a9fbe50d6e2e32467cc101db6610c6beaa7ddce44430438a9c97c74ad498bf9586ab13c448238694b472c3dc6da318139cbcee4a3f370584c14b87458d84e10a122075246d89282dc9270e9b0aec1657f2fa26b40cd1fb267dcf83a60c364091f7ac04111224fa60d88d012648a0fb88089974eea21baa79b282d227ba7a0bb895837e919fe5cd84c4b301f957ccc09fba5e5888b94117ed003e93588c3738b5aac677827f9e84ebad5569a8041764dcac7f42d8d10e6a02dff56c8438b316c010021034280fb4508430861481de184338010b66b08ee17c33054e980fbdf3f46d82ffc04f76b31e7b1a2c3fcbf12e0f7d7669a152da635c1b0f741b84921222489f498c33e08b71f984849223de6ef4dfe0e7326cd85d08dcfa002850a578652252937142e5f727f0fa592e0896fc033ef498159f427b9a2ad26b8518081e0b2edbb0883bbbfe1948a1a4729a594a67e4bcdba0804b86c97ebc79d1d955bf12c2ad19d1ce64e303ad3e84c869a3929b4e419cbf38914af3cc5519e4fa63022e6f9448aa8dcae1e527d7f0b711efa4d8b52b8591367cee3e4b515da43f3ad825289656b3172830497b38b8eed55623bf9d8331ffb35968efbaab7797a8e8fa30fcd1f11cc1fefe9186db517e7f1be595a97a9d3efd9bf67db86f16ad53deb2ef3c7734f524b6e281c2b25b88c0186cc892ea452a50facda365518c30c1326cc1659f55b58d264aa1265a8198b250cd905edb71286bc893e6a8e91d144fcd37954aa52fb944c586231466ca294d8aeb0878860167d8db5fddcc41d1a4876b05872c51256a27a88820208061d00034411251ed081013e36907039b3943eb9e95f25b8a3dc3b0ae61d05eb281ffb5d445ccfccf7b39ec0e50b36721bf1b167c266f2563fa789b0a82f378418992139cadd4ab969d497fbdd2ea216d2433e76ff0bdcd5ef44515fa8f831c19ebba8bf8d8854ce32c29b38a547650084004218c303560d0c17333231a0c7f508d2debe1029448e22e088ffc18dcdea058d0aa7beeeafc8ea717fbb2f44cef746008c00b051332644084308b71de07e9c108310aeb06031fc301c9d70f931429f25f007e10d0fc21c56f04dc88315b6b005db84356104668057210cb4d50f239c010a2e6754ee7f11b68bb65a0aa6e99fe94f2981cb2eea176df5cb849d4405c661c362c266b25ae0b261293005f616a526ca86348b8150e0b26379465bfd5cd8315c70d9336dd3b27bcc161831a4986f89a1c2ddbddf95ba86d31e579aadfe6d23dac2955cc9979c67e629ce63c579e693245d723f916b517299cfdc29776fc9fd953a91f394ee2a729eb2b770f990f3dca63285a8b7f0251f1be64a3ef610465c09ca7184aadf9c60b5aaf9e6b072b9b25f2b3ac8fe32b62889edca0dd91f5b570c341eb27f4c7db95c2e233164f025c9fea9caa54341f60771d0b0f090c8fe9f1697cbe503b744f6f72810ed4a97ecdf45b95cae20ac21d586eccf6971b95c436e8a6843b2ffe603e7830c92ecaf79cce5721119a23341f6bf3097cbf5c3922a4af6b73a44d9b464ff3a2409d08aec4f8b5858382640e0726d89c921fbf7f7c26a88f87de44d142c4ad93fd615cf46cd5e4cb83c6045d3c5df6c4c62e35c5aa4dca0c805419e62308058acca8d8dd2e10aa74f06348ac8175ae4489d1581157171e95cbcfc95022958785fac0a25b12aa63c6dc1dda2a8572ec729580044eb5c0e7392cb514b0d5586549dd951941ab2b045515c4e516a502aa27db024a25a042529881b830204122849453ec8d459510c62332b86e89c60591a692ca96a626394cda390502d2dd06452442193729404a872bda80bca6c051606854a1328b3242c1c8c588d4282c196987a532bfd1bef8931c3d9b1912329f7e7b157141a2fd88b9ed295d2642c72e77edc3e56f19e9a9e92788ac19c519e64f0c4a5a33c61519fb6d080d421532bd2323e74434cd5526f51c0242687c53d26cf2856b6f8778d8957592c5779fecf58cc141c50cad3294f199195ecc55817343f73f838b707bb4dd3340d86acfdc8e0d7f9636aa61d175ae0550b949464444db995a78ce803d9f3fc6b1087ccb758dac8dd6fbbfb2d39a70e938d89a182bbefbd57a5badd54262c5f70114edab24f136e45e89cb95c244b1379748cb6eca7a2c0404a918b4c3df8f63f20989cc0e988082d92d881100980f4e0054989206400454a136d3e0002c750c1f7634296ccc77ce71ab264be3e7e6fe7be37354ff5f6739c677bcf7b954ad5e9f02d42592bb5f7669e0a65cd87e7199116e919dedfa00d53fad1790245b31911d7ce0c70b95add3c0e8bc7f633f770a28dbcc187dc9e682339cefb2decfe06a519066efb0b701f004eac5abc6bb8773b45dade9b9c165c6e194a0898ec4f43c11eee22376fa28ddc7ece1fa4ed4bfadafc09a6309f9be50cb9de7b5df0e1b98adbcff40ddfe6f53a445614955bc60496b3ace89509e0cdc9532bf55cc3bf9c734ea0f6e1f3eba473ce0aead0d1b13c29909d0a7e60f53c20ef0bbd756cdaf785a048815c6bc83a40eac16f21057e9b87e910487a5e0af6b5b2ee08409d735e70051aa67ee1922da767f86f604e4ed7c0195bdf8e368229dec45962dfa3df174ed2ced166c0e6fad910a8c77b5707d7b375df7ee2bd329f0d590700fffb1640f1fb16e68f50038e617ee17e4265215288f4f7cadebe70e8fb495b5d6b073886f925fba07996a0fd56bb30dff247a65f430bee08404f30c58932dcdabedd5b3f2f5ca25d5b29b52ff0bc7089a6bdb7a3766215f10ebac30225a7639b59286f5ed9dd7be190a7fd9c2594f66f47c11484b20571689c66c115689830d90b97749bbb73de57c192f39e5c4d61dbc40e1eb7e47e0d81eedbec3d9a137bb6ac3f97bd05a22531f86417c62c4472efc5d061f210f7372c89c127fba0255cdad94eb2ee98c9da9c6e539bb842ad1a0a4b64f0ac6995fa8f16d2bc1a511a43dfb355878ba5509ef52bb8a3ea787f176cd879feccea3cd87b2f8eb59abd2d7e3dc3b7b7360492c173376705d7cc59c1fe76fb92b3dbbd7f2d0e23a87f4582c91724524094ad6fbe89738e2c04d9aca36edb3b9036450bb240c38eeaaebd8f2d851536eddaa7e00a56ac9b8f5ad3204dbce93c544775b97a8ed68259a1c300ddf7b1e59e3af6db817454a0eb6395ad7feba8323875fb9656cf53c6446976ce0d155a7077aab4dca66c4851f67fbff1a62ad869723191ac25fa08081545903f3a0726a01c216a1ca8d0402c6a072c2f2aa467b17c808ad9bd028a115a484ffb6818e44a4f45e840fef89e000184a4ef3f66ac9054ac2e807e6ffb68e84c440029223d24a10be9713fb860c98bf4100709d2eb3e1a3610319845793650f264f47d349c4e36d04411e921ab09d2033f1afa912aae48223dc46182f4521f0dbb0812a49817f37938697044d230a8ab12841222c81f1d86f40e40bf873f1a4e2b504610457af839417a324970f9a2fa6838a1d860035a480f271727a43743b3e33444d30f6f1af326eda9d4b3833162690828928620900038451123690d83b422124841fe00395067af17f9c346112f4890f56918349bc81f330a59c525ac7ea1dc6e0511f963860ea458726b1834a348d242fed8d980510abc8a2ba820022369f85d417ada57c3a0ae018827f2077591d305507edabdcd4daf8bd31144240d3d2aa4879d7fb850bb179167dd4262da53c95947ead4a97b9dd4e7d439b2b6d2b7d6d2b754dc5cacd7524ac559eb9734557451e545bb76e254bac5f6943e67ffde3b7f7cf3717bca3d0deddbdf7eb3bfd97761b3e13ca58f0f0b61b6e28ae26d85ffb3b6b5a669da9d5387be75c7deb66de5c6820b9bddac77adbd7765e3dacdf1bd9b063077ef739c887dbcdbb64deb3570dbb6d19e95b7aeca0beed5f6ad6e9c67ebc4cb6d9b666f374513152848acc09b0aa8ef53b27f97257fbe3f7f3a71ebbe7bef5d2c71807c34176af43e7deb02a591edec922dc842687fd28dc67c1923d3982f61641af3654ca64f27cf7d8a1f3f7d6d52b1e438ee2d4743eca3a5e23775ece316e0fa9d4b1a997ef9d91bf387befd084860fed4b7aa4ffd7d964adc3166d5abc41c1fc19779fb36debe4cb8f9083e783b46be5fc6c8cd6d3404ea8c43a0cee55733287e9e7b9be7d6b2505d70df3a80adb55fce184ff97dce13d6497bd294bb7bd775e2e6a3a6559aa76dda73c35bf6adca8b2a0a7ff86f9c30c5b266197abaa74aa17cc55483548234a9f56bad150730f9731e190e5e32fdad0031d645ec3c4eddada8a36617bac8ca82c88220c95e4f2a96cb9dbc5d9c430fc7e7ee6bde31fa48c5af829b0c31c4bc87e5cecc3b3672f460c00b330c1521495a8a79f65438b4c4f31dddbb588af93e48e3650ae26c8170ccac3d8ecee027ce39a3dce13b68c8f7739cc75bc17774e2e8635fdf76b4ffb57785512c6f725631c0258ee7886329c3559a0bc05316444876afb5863976565007deb6af200a9c1843e7943075686deaf56f4863ee4c1dfa2a2b70b913c589604c420a21aaa89cf3396250ce08a30838e2cb10ff6588413f6a502922462eb1ffc1cb781962908c160042d0f9181f430c02e17544568f18e1c7f8ee4fcd3bbbc9b375dfef93e77e7b1d02288278b196888102bf306719646102dccf4ae9d7eca55e46cc09593d72fc75831cc4c89c17226584ac1e325ee775c4a0108a2065bc10e9d9c8c41ce1982db87fbecc3bcc79c0aff99af7d935bc4f2cfbc1f7c2ad67d055cd5310057005ae9e867eb4aaf95953c3fa1bfe35db7c8be5a7a4e463ddbaaeb17aea47d993ba46f9fdd6336abe06fcea634df670d49442ae7414d6ad67f4afc2ae67d0ecb1de2664dd848e247423f724e751729e49c37e4f7ce1db31ab7c50e052b322fbfbabf3d635e8b71bf9e8af46e2423fe972e2ca2dfa115512d8c8794a3fa24df1a297398f0bd19a9ce748d798d9bbfd5d87bbfa8bca065e7615d9dfcb1768b28bffe23caaf76f30ce43f3feee729e9987c1f3e2fd7bd655cc566c461916eae35edec5a17a4a5393a5b689433994433994435926876aa71ab067312f1932747440143ea7cfca8c8498d7fc01bfd9e6b465d9b6c862166ab531753a168a0b3ba95f1bd306357f9a784fcc91b7fcb364d9b26565c598d70a0688d4b01b1280747659919d7b7d33cf2361c60ed7428888ba881a0db5ab4bf84cb02f4400ca070c413ece604200be9b9585eac163c6801b10a905564f1d6b59267c1d0d0b65eb37ebc21d4dee75c28d9857cc2ce6c87b3c2fdceb9b71af6fc6bdbe19f7fa66dc8be545bd2e0b44e18adec5a31cea9bcd3e27efe18eb8970adc2b47130d10856a0560ebd664936d532c9485b250166a63e28eb824142cb521804f00200a570096a75b71fe35b45eec170b268518d98a360a2aefd48048e3c8e33d3e34b8c76700dfe6f37147dccb4271afec6fa13ea76ff6cdbc01605701448005cbaaa394d27a350d0b495d881ba8d5aea6615162edaa4a21436badb552eaf7de4a2be782d95aa9922e499e94d05a6bad945aab5d4db3f689d6ef45439f94c028add6d65a6ba5f4d65b5fd495e0c05e4dc3422ba548b7b15015203d3d3d3d3d3dd97a9f2ec5922b45aa97e36a05539fae7691923cd92f97d2aa21fdbd286c9a76933c49c1b2022c9ee4a9933ccd244ffe510f582c1d12dcbf715db8ae4bd7811f7c30eaa13bd2f1d019c1fddd0e345d91171d91aea8d3a11bd2e5d009e982e0fe0e871894c6f8c01e6db4677ea52d6f97c0aa73f2d1a995ecf9e616e96cea7cf1d12998fc25fb6dca9489f6c45090cb9af97446da7692c272a9b9e81724cdd5c1aa70ed395541814b0acb76eb582c16a3949b5514c870b3dd2bc64b3058d47d592fdec30d5d187d5d187d5d187d5d187d5d187d5d187d71ae0b7b71306b446cfbb6afd65a2bc55b752b57c95b494649d99f1a5124de7399eed25d32daa08c6252bc89537eb0175eb29489c2e875516a2497379373a2dc8c7245ce13e38c78cf77c4cd6e8c9bdd1837bb316e7663dc2cfb5bf17b7133da18c6157db31a30f539bfbbbbbb67759a2a5dc8aec5c7220b758d6c17efb1168c97210b46c8509ed788903c2f926b841ad9a8d7ac52d2d72adba89925af0a05d6bd7039669f77bcfec57992e60f18efb943143652d83852d83856f1ba60428a6eb29729905c72b7c8954b5a5494dd6a71114451d105b8ef1590a69706b9acc133cc9e1e6a2175a83615fb8b37714a6e7a1d15827f12e592265d99b7da07fc6df68bf368ef7f93e60f18efe18c78cb3f4b962d5bba74f172633796fd35912bba4cb7c95da249d9bfbc4bd99f265125efb9b21bbbb12a988b21e14d9c92525a8b7c8bf7b883e94287bc74c9e58329ddcb043373915bf4a820fe6edfb31282be3ae0dd7477cfd9b3678ff9d69b4c1fd31eeee7ca3e87b48db445c3c7460a3132153b103081635403de375984172e71bce7c65bbe126295032b4f225a9337dab342c2b40504f7cff7aef642dbbef9238e8f50b869f2ec6ec0fddd0670ffec86707f6703eeef3490faba1a707fe7c2fd5c18dccf6da1e2b4c0fdf3277355300706f77359e07e6e07b89fd341cdc77db1f9b81cb03e0e07b89fbb01ee9f3f3779bee7c1cd7bc582f613e0de107bcbdf6b676a55d3b4726127a0f9bca9a3042e57de4d17cbf5fb67562326539724285916a6d89499152d5ba2606192949692609306d38733379ad58f393b3e4c4d9c8929fb3331d5adce14c0e64f98bde932da973689dbde6631d2383105cf9cb56297a6d824efe9204516d6c4c66c93f3ccac93f34c991121d9df6af11e27fdb94d7b394f69ad3042628f9cc74bd043e0293382246f6f13e6ca3475fce726b3f2f14b8de666f3d1ed3401cb50d6b5e13c3439473dcd81d7cd0e29e81779ca764092fd6a54c9797c05d9dfa394527adb2679cbdf7649d9ffebe9be71f879cb351d1a7625644c90e49a3c6542c0eafd1a3651cfb82c1ee08de374b58a3dbeefc42056fdf1fb205615811030c9a7adbbf2a1adfb42a4f7db23e0fe26066de5cac79293c4f55ba0ad5b4520044c92c77d2172041729743b91d5e363f19079568f263b71c90490b6474afd245955e431d43dc80314839aecde7ba16b5dd6f6c42032e23cb3051ef072bfdc47f95edb9526c1ae12f9520167ad32e2d00c4d729e29638292ef5325e789c1b2de17bac097a99f7ad57b5ce89af96de637911ad1d6fd99ed9bdc84c82f14226bd8233565467c51894bec8321ab87cc832f23d257029ae4be49ee3385813fbf97f904a41e1469003ff532220d4b7a809f12599586d47fdf4f5226ac61047cbcf7372770d9ae54e802ff868362bbbcefbe1722633ed6628feffbf7fe5412ab6120f8df778b3dbe366a22dabaa038690b6c176d5d4dc4f53db1e3aa8bc6dcbd2842fff6b736b2a537684ff7f679506b6753b6960b82bf075ffbedb9f74416ed4416a51f647b782dd0967def8548fff6c22e9cb1a6f72971a42ddb89b6b3ffd40b2765f5f8defb4ff4c48dd543ec4a9f39edea2dcc02e169ef4d55d6bef4dc33e6e47c34dad23891c38175b7ac3d177483b6342edc615d1fd6fdfb9b18b4a4db444c5bda078d9a86a78e7d99a9f3330a03cad9ab558d66e55ebe1965c4a1c1df97cbcf264f199392bcca5346a42966858603ba40e001f367042816ad248a4a08b0a036682f8d944b9f19832b666c06a36f70ad6d2db304a659ddd8a0e1e3014cf3941181d2ef3420f224c772bcb59a37b827d4ce118d43f67f27d97fc3ab1f02df3c65445490cb7116f5d43f372201b9ba108e8f951e510e805aeb737a980c444517ad13de9cb557f7eaaec2d8d34516dcdffa71fed479c4003ccff9d353a77bbc5bf3fbc5e611dbc6a654e52ebbdcf34b1172bb3b9db32cbde3a3fb78cbbffa84377cf4faa54ffde6a9a2d32a96e10d1f1b5b077c401d0cc8f47d6cdc68eae33d4dd2f0d13fc0824b9fec3e369c877ed94f9bbe8b3eb33557322098f8d8a4f9e16d1b77c6189a0a5acd474e7fb8c54ddbb452285b91a5bd50d6f00af0f735f41e0cbb2ad284a07804d3ead35dab623665aecd1372c8844092e994d939411628a6e0198c7918bc674645055104c6542a4b82caf44e77ede70c4f50c147fb79ddd3a736bf19b91ee36306187cc426c0e5967f3a885980b7916e38e54d0c0c3ebc454b9f0f09ab10239e00854171de0ba210011fe97b3954104f78fb7809cb1af042962d3233305663eae9fb9821c8795e3c4de11c2a991918ab91f6b4eb8311ce3c43c7940a583c214b0e1971fcd4e3255ccea85478820a3e4e197e2af3aa9fd9688bbaf8172fc21a4e1f277d06380fcdd31cf3a91c1a2ca39a99212a26a4386627258ee574026badb3897e13d18781fe0cf4a74dc8ea61f34bfc6d5e8864852c1e355f23063559f342a48f19c29ab05db445df266ca222a32cc2e0e3128c3727f07ce12e9c48ed80b5bfef69e30d59f377682b644d31859a574b3a27b0774de0fa134c219ccf52a1a775789c275380e9b96f43886aca6e90657f1eb64e2053b22b413494fd697c4e1d340c985c75d8aabdf46a3b3ed96768086c32a2ce28bc705a68d756da2b26be3bc4003570028bacca0e2da001066040e440041a988204125415fe040a3b45f6ffdc89152b03db8155135988891055a44c4992c3152eb21c9db893237ee4c08503469c804a530894586dafcae41df1c4a3495ac0d9a0b4d1f071177112362c91fdbf95e747ec7849e2a8091d38500325bed8b00445033c9cc045963b3fb0207bf61f64d9bf3c653f94e087a4ee89c9d5ca756f9a58e22bdad087cdf36fb8510e9c524aa9572aa5e99ce2f412cf57d988d9bfa7ebe662e830b6767b7b7b23ddf7dea9513c201c1308c7e3a0d9d66a6bcde95208e97615cb0de38d9b3afd339cd99dba53778e9bfd33776898f9d569adb4d25aedd7adb97c3218402c56e573c6af070f159cd028d90f45b97f4e81752664094204566421d301291404074d52dc100229432090197dc9536614cb25e42933d221c7d4f76e0ded1b674d04f281b346c5ee426ecee8d744af3b18e02a941dc78bcc8026b8c4b9968de78ffd179a58dedfb654f7ee6127964240bde55bbf6e76cb2c1d4eb7f933b7a9d3ee7ac28452a1ae95a987e6cf14b8dce2868286390ea80d95492f817323878ffd5a1461390f18d7a00aae5c643d389163377aa882038c1b394ec841450f52fc0338264f590f2ae881881c685c0084113d1ce92b30ce53d6c30d38c030ace82a300805ade188129e04fef2941d794287602f4fd91126208e28c9e58cad64475e413a9c28e203c1aa3c654786f8103c93050eb8060a17de98702f98260a069904c1a0527de11779ca783892cb15dd01bbc853c6430db9af60304f99112f3976420e15563f7a5c5633af8a50911a58398ab2258a23b2f0a102c5061b84a882042a7685b3974ead7e610b9660594c896aa24357e1c2c20b182194a4c4b6408107ce02cd48135ee72933d224c7e42933a244a679ca8c4ca13e9bd239e79c5303b5a9134ecc39e746bbdaeaa2b53ed5a76aeb537db2b73ed5a7fba46db32a55aa54a952a5bbab501078a5b1b363c3c7c729c5f9cf196d6da7d5ca66cec9d223ae37d7e8ad76ce72ce39e79cbed56b8472d9ab5abed9d36af6380e9c5deeb2e77db4e7be48e33435a09ff7ec6a97674e9c7c819ab456992ed8411c3699da6aada5b5d6f9345fadb5d65a6b9d552c1d90a958997873ce39b9279e9813d76c755561cc7951983f5f68d385463327f744d3984c1678e6669af1c036c33d61ef55799dcce77df562f0c33129106f3729cfa415a5945e23a33c6788c00d724f187dd645d2e6cdc9d18d724e7cf5561817b85decc27e524a29b5afb72e92703f2dc2f5050aabdd1196969b373d9b1a67d9f4cd37c1299641b33c6199fe8c07282ccf67ddd81667e0d4526b2333c5689369a594d2d77ce26a53bbe08e0cd8baadb470a42d1a36b98a3b5c469b7c2dd7e171cc49c2f29a6470db7a7b58615c840b17239aa820054cec10bc6d5ad4a00628b87265d5ddedf692f70a3984337145e94a131925273025272f27a55aab5d72aadf8bc649e986dbed94524a6bad6ee9cb4949ca95a32b448e9c9c9c9c9c9c76a8cdc1c1198213aef015bc0355b7cded923b69573b7a3925d17e8fb49fe2ad3e5e71be9c5e575870e5e5f4727a399980527a658952292895a20445900036022296744a9d926e084aa953274497d42175417422e88eba241d10b585be789e4fa66177da9dd25a6d3ba5b55a5bedbd9ab66d9cb6715cd779ded779df0782a9540c988a89c1584646a59aa1a171e1e2c50b18abba5a51ef958c6a668686c6858b17302a0c18d41b868b235ecfb1feac1fe41903b3f2cac72868bdc93923f6a14163db9c87b5394f8c1acb0176d96a9b3b72ee884be25ed58b72a7b55a5aadbdf5757de3aec781293025a39aa171f1c2c6674d4d5adc8a17c363cee44cffe1ca971c168a20bc504a4ac271d1c5fa91bffc8513027714029794fd4308817b714ddc14aee9331261e32c14f76a920bdd08c98b8a36d88b1c1386200a9f93080208200a9f130e0054dceb4b146d3111f541d4f79a3f5eb264d9b2c50327f2e006447297bb401865ba4041dddcb04268e12d5a2c14cba6d51a41868c9c113a8b959c64a874ac5746412b95a11100000000b315000020100a87c462a13894a6b1347d14000d7896426a54988a644990c3308a8118c6186308308018828c21c628433536006cc2b3e3b141b12a1d4f8797fb6aab617846eecc6d28193c60c203843906d2d1a5b10187d5baf848afbfe5cde06b474f24139841fc203477cdde173d0b3979d1529c6517db8d4e5d0d1eaf49b7d361eec6c14c112a1f9a82b68b4db6be9c4f9fc3a01753e8852965fac68873aa80a9db0cb456f1e63765bd6d02b54f295b2a4e60ddf5ae0bd966db91b7cd853844f1778e73672a1471429fefa8cb74709e20718bd6e9b17fe724bbef9a1f00ea7c9d51ea8306b5564799c261ca6aadc5449631d829e0559db800b333ce3c2e801bfea12f07364f8cc0465d358f719129421d732ae15fc2140486fcec91d89541edf44c9af35d269baddad04d4c705b3870b627de7150f5930c5faa03f4e497d20b5f439909a664c7980c3a70669fa6a746ba0b8b7236975b3beac94316061be741e925c1c6f071d1ad2b19160132a82206b4e463840914d49250b6c18a6e22ea1fb81f5d9804666c422977f48407494b240d9a06273e8052af125aa01919ba6c76a2043ae3501a42539973688127358254e2de9db0439d624448a7c12bf7f029a06787afa6bb1758f893d4652cd2c2464ba5daa6d826b0dca206246f64ba34f85ee25e7e5445e66e705f640c8ec069d9aa692a6df3165ad35282e2986b6ebc78c63400d9f31e4b62e334553e6ad114bbf9f68aa1d700c9c67f8783af506491acb4e1e16d0c7c7ca3dc20c34b67f8071edaf683a1fbef16588a1c664a684abc7e04afe6ab72f2776b20ebca0f34fc3fd19dc02f7e6432a73885e1ddb74233f1cd54465bbc9f73b8dbc4e575df14641ef3e6ba66e558b0873630c519bbee2706d741e0e602a884b3348b380b51c4348079c14bc1b953412c5b1280b6824c44ac5257faf9dc3c3a1dd141acdf25012fa5a8e52013802c08556cb55063ae34c030fc376b1bd52f441c69d837e4c691938110a193f18ab57bba625af702275bb47147ee0f7bbe3f04936dbfdd16073264584b624c3b9efb93434b01f2660d7535918906dc1b4624396684314d943755dcbec549dbef4354323a3afa8dd105d40335e3c6f0d07293f4156aacb2ec8ecfc0560cbd76e46535caa847b593634a968261efdfc514707be2b136cfe0e08a513f3ce0caea52c726ceb989914e3853d12ff591b8812ad1bf9bf1239595a3deae4fbdef870fa2ffe1c4b387a1cf024f5e1084895cccd90e2aa8bd09a36814d568b27a62abc7ccf6df9b23bd5d5d380803a315b335c08aa88894c4260fcffc5355f1d3a8e5f0c5b380d05856457d44d76063b7719f88f399a2c37a15f9ecf54972f5518c5fbb3d96e8238be8d86bf529eab07fa0adf80703e1f30f4a1b02f0c5b0ddff0a6593523c8239879a2a154938ac62f4b7369eb4584f1813dfd86ac3821343919d2c6efd58a1ce0aaada33b4be6e81f0fac74f435b75c6a48625b2fb46917481c339aaad531a21cce00354fedbdf2fbdc5e4f5226dedbc5a577542f325e122fb845f2e3262c635f69b37de81d62b5ccdb472c988a2ca2031ccc139f4aae08a4546e75c345d4e1d14d1389a74f3ab4055784e4779abbfa51126a232e4bfab17870030dc0e8b14f7eef8032b5dd401e1353fb2707c4ce6a9a05f989f739e6c897eec5f1918e1eb81c41827e67d15db105cfb3b2b1ddcce88398b0b96fec4d1fcbcab97c62f321e0acf1b6c1a6140dc670399f166ccb70a61012036187fbb2aba189bebbd2b7412a7342dfec15a92d14e9c33e2b1b6de4f0f153dc288da840f7a9546b04b809f4660a11838c809011dd301ffa1075015b41b17095a053cf13d3c119c30a84cde4aebd37ad3bda694b20d4dd26e71c5b8b104d0d932311a753b9ab37fcce7d4ceb84546d8d493025d0f3c96316852f9070dec75b89ccbc60d0d25f35122818b641ed6dc41f3c0ec12f674344304d94981ae7c67c6af047dc5c316bac577668cec383b9c665148688de5a400d5c6eacdf4b6c4be59feffdb624dd726b4b4b2b8c0fc0844df44c474d244889af7f0da885faa123e54fd46adcbf40099c032d8a7fa7eca4277fa6c313baf761901a49de6157b722be7d256b5c8dfd9b060b960fd6c0025b9521eb9a8f4a3a5142162460d105f841454b638f0c93f0210e9910eb69160d40cb51821b3de3a537e90ba2b659e7cfb72aec57dbb4aded899a34c3a94f0249f20a339e82393883272e2d1d7d81c219604d3d13c4469508bdb0ef6cf0029440acf43b1163be709d7ffe1bce353df6a68350b8b8a8330b8c948bb0d337ac7a37b234c0db58287f21b7568116004a603436c5049365592d50c4ac3d8b5c8779a426809d64a69a188234e4921a6592573566121df3477d5bd48c225f4430925d9a87de15f43894756fb222b50fc51e555e4a1434219bb079b096fd290797b8dea8165a701f76ac821371999c15aa021ec886eb7ea3571b89e0f755ad3aa36df9f5aafb4d217e591965698854967dc0744ce200d44721c752c8f2acfa05a921f4efd3a5538200edb82b7c21d486684ebca71f400a5fd791fe85cd25e6232fd830b9f7224091298befba8c69827aca409a875db606f8b69d0a2c9b142fdcd40f64a5572f2226208c740188abb410fea9edffe845024d20c523ca9665c464dde366b63656bc327bf09c4a66f7c1dc624a5da6bd54938af4f0783ff8e0b432e869599ced5e7c61d4355095631c014e5f13e59e99a319bebc8d3a728513587c73b14a91296734ea3b76f86e64c8506e6971425f5ecb7c4623c67308d769784e42599e8b0608ea131f557afeca6749fc0ab429398805efb437b2dedc183188ba13261f0f78b5c8b06fcc29abcb473d2bda7c5ff6606cd88db5e878352bc5b670580d899aad01e2dde00a547fb7dc4ed0c9de7fb05f55e3e50bc0c65f81a87d615f1090782af231ed8eddfba1a16952edc75e7cd70ee633f906ebec76957459d45ea6dee0933bc83cdc6784f70b580bc56b193bce9d1f6d0009ad3e88ebf42234af9c10102904c15f4f512f61255702f1d4112ed1868540142dc9f5977c130781d55665fff0936a5b92dcfc752fb8dd9c75730b70a0f3f6a40acf0de987da8939aeff56ae8a1feebd70ce2d6f9b726977ec5187f09dde1b104f8c40aa85f7284eaf7f457fb451270ecb0bb9cac5d952f19fe58cb7e452b22ff8d36d6a284328d5330200aa8ffbb994c3a5801f55de1a908af68b407f182bb03cb40350f48c0d2fc8b797c825d0012c6b54194ee6ac6f32036e4024871d7713d1709ac550fb0bcf63a7949ff21865836c71e5dc1847bb3bc614313dfa391e06e47f5752f032c32506ed532f681f5b9d7ba52f39224a6a783b0d1934a6080e2fa069538b09031ccb6b3d4ee1598f5dbf394f649c0c9bd5b0ee67beebdc6bf68205655f5e052dc8cae815d9c5a1b6a469aaca7084a5b4c686c2ecc1939bb0a552cb48b553018ed8750fedf3056197cabde8438d55be98140544e3164b244643aaabdee4207819ace4205db2cab9a4d6ee6fc4a4b8a6ff09332af51832e0ddf11235677e7ed52db00e0f5511f26c5b16307e1fe99c64723e584444c5a12e83fc685e1fb536a677abbbe73d3b3349a3a3934a85864404fad58e33e5c24ee5501b20e66bf1958ffd0ab84511cdf9ff1eb3ba684ad83063d09de22eabfbc5799ca793cec55fa7c910a429a4382e119540dc5a0813a48cbf8059c4801a4e82ed3b012be1cd39a232f11e63fc3af8a3e563e37ac5a5790d3c66a04ede72bd14580eb569f24675bd3c99270605bdb305df92f83283ff78b079afb8a1da4e9a180acb1cfd267501947bf0bd8f6ffcf7c104d2f9f65cb6c2fbe2e07152ef3c15f2535d7a8ac51a539ff3632f2925bc81298813270a87a73abfea574919e030096b8c5fcb57278a31e54d5ccac7f27058bf8c722ef035626315d186d2bc853ff3fd56ccbe805d7a2218049a1b10000fc6161365014c4072272f8682e554f9b88fec03981481c6543f582e363c1f8665496573e118e0ba1c5c6ff4d7de902542d8fce6df6dca45ac9e2110e887dce6fa662741b650467051490fc9e38154363d0dec90e4b949e9b93c446f02ba00cc923f22b900c6523f8ab8c27e14a5c5dd1cd2c489ca24e1ec32b9a520d879783b1d144857059ba45cbbfc6ce361fce721fcccd6d156e0091bae97dd6ac27a23e789be9d43f69070fbb125328fefb562ef8adcc0a11e97c1e87d8b8f255d6f79a30bbdf86a0a2954d46d0014ca5d68b1a5ef2a6cf6715830ff17f4d7cd753ccfb959eda38cb807f90baf886b4af1e970459897bae9af624b4545c7f2204fedfaeaae9ad9e8eeae9bfea3fbfa92236b16023d78ef19167e06097d68d5ebf47b5fcdd29a266001f8d8ac3545829e8ac3a52ecf9a24eabea03897a363cbab38b4fd43d5790a5d85826c3bdaf742563c05d34aade2e2849a50b3673136e9ecb3a2020da62e2d1a8ecb8f80e48acec489cc4408eb3686ee016b90a365166794ca2820592c8e3039b64d23b09b2a2b044ef81acf026b4f455ef29baaad22499d865cdfcbf723608a0a2aa9490c07602501d2d53942e980d96303419f0c8ee962cbc07cc5b74b9ff86fd18849e91a0ce9c6d81f2c5343a3fd241546bcff21f4f85ef343a93f9d8d446bac07a187c66c6c72762d89a5c66132426ecab90ccbfcd9efad456c7967350f26f8922b62865ddfe5def572f5fb1eddd9606975f51ebf7ba606b41550be411f54ef1d9740506b9d8b2fcf2a6d502a56f7fed3a01b942642609a15237b00326ccc5e491505a057e066bd00822b5836af55ae66b4fe5402671ead042bdbd1492ce9a23996ac062d8ec34cecbc0d5e0ef547a75f45f120cf005ff05926c271222d31e83d54688770d40dd0cc63d90c826f6299f0ef957452fb30a2bf74e47719c5adf7af00203dbb407c517c87f3395cb55ae80ae02735f7ebbc44c9bfbfcfc3c4d405e0952f55dc7f42a617b6be0f0d06aa222a03e3628e15dcf9bb54c71a7f12d2174882f8b07d4cda475ba3095e318f0e04da2b1c8bacc3d75be256f0aa6458809c31640655d7ecb1c57928b916d8a1272ef975b15a5d1b1f316a6ab74059131e29c7cfe8bd2431412301dc2486b0b4c27bb8baa579acac162046442fb800515a6ed2817da78ba79e3ced4fc2ceac8759040214bc0ff80b6b860aacfc7e16b6ef267594de1a993c61165cf38159679947fb9931a71957c3821c911a328e282b4b511a1a4e36ce38b4aa16b12acec7eebc69f2ae10907855a0042607ff48791736dc737ec9029d4db864f8533c5258f626601da1953f6fd47549d11cbe23af6440f806f3ae0a94ee7f8d403accfa7874a9d056ff5b331b91501b5966bd3052994412d53de04d9a43b6463cd6cccb38130a4abd3f3f1789d18537fec547540b14605fa35af6e39bb27e8c54141259369d3d589dd06f0d8149baa5fca6550d3162bb2c3d1807c89f39c44bceccab585ee5c660cbad2e9995a7df9f87c8ee2fb38dd3fbb8bbbedb1dab4226d3f21f94790a39e525bc8df89405686c7213d1ef04464e173c99fd8e4f01cf134547df72b21f2af6cb87512d36b291f6601a1c298686c99371407e89fc2d9a1c9bdc8e902292dc26681adbe9961db4a79adcc78c8f761e9e68fa455857a735d57a6d35224f77f0e8bdf48415e48e507c1350bf8fad01ce8b940c9ecc5575a35a002754e57afa82a90cddb07264b43460b0076684c561ae5cfa8c493f56a0c6129aa0e4cfd15dbd7eb56934eae84d6824331058c4be9f876013f888be68cfa678a9aca454920bf37ac176cbcbd6e76efb0a2b0f5fa92abd67dc764e971652ee35f48b3cc1000487dde0e8456a95e31bbf9b04dcd69fe135cea9ecc4e7f2d9de7430f031e91201f30bf02740b5b799c903695ce427c78c01de6546055dad07893cea37626e9ad52e54118dcef605756c27781db723067bf66aaf1c11b5d40999b98be8aaad0b7c480ce845897e724f750fbccfc341c84d26b0c618ce73c1571e4cd8d82d41e7f13acba7e43a1f9d2c3d26f7eac01b7ba27bd961ca3b7d6b1bd651db38659b14b2af889f49ba65b9564c4730d13e991b17d6df50a97f4a378a30b72ce12177441f360eae8df938633833c350a09732c2aa4aedb8fd11f393b2f7073e967ad43f257a15297f4bdafb7343fb0e2694e81b27609d03c9e3b2adeae813bfceea76a438a5594f8e84cf400f479b4512e2c3cd1eb87c883f423e13e31520ca7fe8a323ba5fd76e887129874e2713cda8a0d201b29213bba84e199ce8c0798ad7b4648ccf47f0789f4b23b6a6ab16bc276c19dd0f674328d801ad80e96ac1a5ba1c1d4a86bfbdb623087e320736b359765f423d8b60f3a8e801c63aecc770ee7e196dc2a70d402590d7a99209974d51dd22b4a4c0ac94bac9a3cd67d5f502cf4d6be97a94a23c0839e5f1f74fc2a0c4b150e4f846686f4007e2ca10a7818ce2111c1290ec8672821404a5cd8881eb06cec8629fa8083865eb15ff02382b061c1527f3ff35f85dadcd4004316cc5d5deb92fa567649a8a8b43d7c30523eb86f3c09cd7fcfeaed0e0ff12a0418ee502467ba5c64d9210c3821ef45a1cd73b33689175faf5e14304b88e7b94475c901d5c4c67abc59f524ee8422629411836bb026abcfc7090101a5275caac71aa0bcf64e280a4e9a709c8e61580d3f9e50a81071cea07bcc94c68858910489422b25514b02bc11e68ad1101322e8475bc7dc997340e4f54842be253950d382a8ba04b52ae3f745d11bb84ec06347e5c8ade7e4eab2ced5aac26e34a17b473bae4c72b9f4860d5976479bb2f73b84dfc3115c28ca0799716bdc38718d32f7161ada45db10ad6f529da7f5695b8acfe847c8022eeae972282255f55920d4f120774b558c25111a0d9fbd21f4933365afb793f6c062a4bbe6337288dc51c51916b031212f35c78ee40287563388bb91126ef66a2dc1a99e0bd7bd2df3d5ed87e905489bd6fa58868ca6a8d935222f9b63a09e34ba6976a18d9789b17757def25061eb9447f47d27d7bf029e2ad51488ff085e4b81d4a5a4a3ae5da36ed5d6b988980eaeee1c5a9abd6609e6a9422c84850eda96d948b8430634b0c2b774221c8ba1dc9be00b874f6dcf5a89e44a9f84cd438005800437e4418531bc8a92c60178bf3ddf1df1bf83ae1497db29f7c246474b6196ecc1b7bb640f1b5e5cfc3d489dfcdd5506770559d96e7c362caee2dec38bf52a4848ad3f7b5535d1ee466a4d0075f57ff79cd27d8d1602843a6b4e2cacdeb84b842607687e8bb5537d5a6a935b59c2b54f4adb14d49ca82a2d814d3860384fbeca6f70542725c0995365dc000060a8097c2262dca47f547a590171da85e72394267b2c4acf93f9647e323273bb59553159c88c9cf8c1e2f0e0278464da19a264365b9f4b7e62c96a73767ff52b9cacfe060ca61f338253292db2799f6b80e2554023d198cb0a7a48d686f4db5db7e764c1bd5de0218a3f700b83dbc82ca0a9fc23f68ec12fd118f0c1e9f897cda8c9bbe05d1b3bed2f4d24f850be8b3f2b0539535f3ee100ae85dbc395dd38093066499fb6c189c30078d48efa7014231968ab57e086a02b39edc470aa7a656ff6609152dda3ad023ab5737939353b4afd4503312601a4a89b33ad53d18d154a4e5f1d73f37e5db89f5860f2f9aec82f5d0dbe08e4c3f572fe26ce4ba98580b0a74cc36d3c114e111ccb05f473af307722522c7d2fa70e362c18e8b10b71b12374b949f17f4bfa05672081a8f1e4b8b7a2c2b6274dcf82bc7504f2fff1bfeddf2658cf3fe27e1c95d82affbcf75191ff538d4c822304a005ca3a27f247d5b0bf21f96bbf1a6511123289a0a712c143dd28a6f17871eac30bc1f27324251d48bce56e4d4a99651eb06b8d08f717eb0ed193853590538121036d2f942adf71217bf0f4929547c6b15e18a513ca171cab3399773ec3ca7dcba9d019032f9f6c76922b073337395619e0b34bf590cc08f887829e0fd5ab90e365e9ca1a43607f3d0090d5a134d7e6c81f828994e37997da468449202821c5592583cc8f939a18763d772898ff0767b3fa880019569d9fc82d044716d6db8221be3e9f63fed67d7e5c5b90e7582b0f6cb6e98f20ed8a6a115f0b1504b69afc2ca900cc7315c892d4b59197b2836585de290caf052d62e7f56d4c56ef97b6c083613e2a68d3efb8077169b4c554822e8b998cc77c01fe1eb8d30bf45809118305a6002d414cf36944200721330243e52eda4a0206ad54da16010c00b66222ff17facda7c819b5030a7d79059b14b91968062e6e66e184c0998337448d861747f10074988838f94b4e7d298f8688c7c177be7a0c7398bc25250c9a1f7f7157a96e08197ed372b406dc47bd3694fc3664dae4828bfc5589c6f44fffc23fd3198314bbf04221f75a0b026a9881219d1bb4a612c2d6d090a6619ec20a339207e35b2217291404af136cc1f64e15411fcec319d831b5a3003743506abddfecd8458134461a4ed6b55e06ed05e978627447cc8e674560fe785e0f55d9be77ea110df2b68817b31447ea5978135bbfd676ae6677d956e05864406b425ca66d1a2e4a9ff12518d1d6673fa9bc16445021bb0cea2e94c84c482f325419ab2157ee78378ce659128bb5c49c5ad6e4617c7ea4e0c81eb1f8092af6a5114e63e894b45aab9f0afbc27d1297c48dea0536de5eef03171b93b9e7e165d46416ac35642ec6ff8154468f154fc0ed7a0a408fc3132bea6e4ea78c1ae36c57fe990be2c6b6046ea30ee758ed536c2a69d3eb166419b8e9ca06d2eebd7aa31c9de2b2f17fbf4ef17f2b73c54ec97a5bc011218f8b3c4891a0a86946aba4a0d8ccfc1fa177955697137e07b9e88a9bb8b9f963e63d82e3d8c5334ac76f66047b2451d848faae97170a3f38e27b674194b84e28f7af59f6656219675632ade868a9e9fa83c47f220018461bcf929dfb237e309a4d2a6b8f2814336dffe5ba13e9eba2944d91d356442c686a064f8a5f34ab39aa2268cfa1329e7c0c0bfd97beff217c543d02a3d84ec3df1cc42d027fb00275fb6e4f4e79a1c2776c738b3ee74321f0448d2554551391e3e5b3938e8ab1f6817339ae786f221e73ec3bdc5bae87a22474281d880b181905a64a639367e250febbe4b0c5a4245be1f2a75b93316103cdfd2639ea4aaa8047c4bc417aefa80c1207d718aa1165a4bdf11e9579efe1c5dae31ad801281a60c53991fd0640b3a8b7f657cb0383b3686413e7c7a7d898ce1cda5f1c93a0ab5fe581f83d196895133ec340e79b1b6b1af60fc4ca55ce27bc07c5f2e45a4dc18d171ed3d6516ae5cc200bbf6540a465130e199168c8148f0578a1598ff07828220b336138c168d5e0fd14d6fd49e0be71c3860ad6770dd7fa5310f845114f0f465f482939dcecc745c92aa776e1b2f8d3de5a98670c1514da3901cb37dee01ac9d653855dcf1b9cbe5c2087b7e468573c792cb1a8a8f0bcbd05823f5547ed94dc7eeca6402358d6414399af5ce3841950048d6e35c82559e570d5ca14030f25a2d8e1d6d0ad63345fb7673433d350d9ffd079370c1941f5cb39364ce396b603cdb65248d7eccbf22b153fad7a709345ead35f730b0da8f7f86724ea4218c703c106e306cff5e284a44c66a999f2360aca70a42c8dc168ab89118a2d06b6ca438a8f7e0cf7d802048eb6c2b2e496c8cfde4ac1bd91395788648036474a2613788ad5b3a380646e6443a460d0b312079303e8092a4cc435e1264fdf13b1f9fcdc9c9f94bc3e98b1eae3e1779f83b9ca1b3b08054fa0ad13ffa683cdf22bdb92d959230ea630d5e2632905609daac486d7b8a311bab00d34bbe55d7c49e0faf8e72fd3a867738ce0b57f69580c2a20fd43e06682843660ea2019b29385935dd1e5e6f9866b7751e147c37b5b36f0509cf3e9e88ad987e34d9f487efb7469428a474f23d3275a82df4f74b1ab19e27c0a1696cdf04f8c7c87eab9b7d1fbe94a8015a3a6d447ad10e907d90c7943360272a90a3f643f1b11a927b860d03fa7c67e010b1d8467e232d0dea847235ae04a9fc654b0e4a0fbfc10c22a390e70bfd00bfc7081c818a67d28f639932260850fbf321c11157306c867c0027bfd35056aae44093088c0eb55f7f0b1f7a6f0f003984d0e4753f2280607fc3ea45225e03625aa9358420a8743c0cbb1539ec907b98af00f255581de5d5931d9a5771dc2969abb08223a03a6688404bc5f7bb7228adde046e017812d53d70ebe8058aba3bfc6cec3c108c278be98108055dea2f7adeed5d83548fbfeb8c1b8050baa36f2727448a85c3185fde0df72ec19393be390059039d2e2e4630b1363bdf392700e738c07223a1b9a18fcca631798056b9227653a6fa1b267ce5678fe15b649ecaaeafa866995ea7760a3a8d5e89bfafa149ae541cd399fd765d1471bfbd0453d9b195a40f919703fdd976ee6e1d39f17c1754ceaca44fccfab44e9468fb7ad282ec0016fcb6b91290c3d31cf7a680381d16c65242451c44a429b8d889f3381f12152d14aa2ce191ccb5e2a20605367d450f6ff0e700dc789a4b9ea5a185b79e309db748536c1559339ce8de800a10a040cfd16c726b3c45b9823252254e35118d9dd0f083275f13476df3ffa9d7143aa80483fe50dae0c371b931ae12dfc3e397d7cf89c3a461bfb9a97911da8121e8d0377b79e0a88d4889c54df89063fca91a6ec30b83a9e8f4b5b9d78fb4450b86f81d129687ce0c083dcc05b083641bb2177fd4bec2f04d1ff0640b28fe0d0d62917649c992a56d55a97755616697407cdf11877c494416f70484cd196b32befbb06ac4bfaaf4a1d05245663d5619b0592de753bd88e9d2158d89c3f1bcbd61796a42186603fcf88c9e3b038a9000162a47486c1f35ba0052bc455542dfcc12f86692fd29a4b7371b432899812be838e63dacd2ab8b051de00cf4c408af924cf7b7414fddf07da81509e2b6cb4d6f79611f066b0947a79f50bf483274c3c2d6fd591584dba213c1e73be786459d2f35a22380fa1c0902e9d5c4534445248aa93f142b98805dacf41bf4ed097d008b89c991b6b7929a5d64532d0b9ff715638807ceca328d66c5ca7d13cc9b0cff79d69f10c57407ef326434ee18e5bbbbd013789d6c4900a3bf7e8b0cc03d7cdc6b3c28c2c65bca5cb472f768f8280852a0c5bd5d1f17184f3a6687b9053868fef547e845152f0d6b34ddef37dbc5b62ef0de2e5fd6473dc6eeb8916d61b6afad46581b6fed7d24b06f802bb03921a69d66169238ef966f1d27f1dce5bcea0ae6747183a20d48724a4ee16a9f3c8b04abeee883123570cd81b80ba79f955cea0461dfc149b8b255ee1a88632d41e3661e80aaea5f04a3b000839b72dfc6dd804511edb10b7c956e75b7ad811201e6903073c92b81695032dc76f348be54062a05e8a0f3b2a18ec35484fe41931e654350a7dba8752dd7903434cc3dc10447a6976b010c146a4fd3e1c1b11edf78aaa41c5d6c19a865c5d8e71ece8832f1664010be4905b8c5e316fd094ac9ab455490e8de5b43ed2147e00e9114c66b8c60a6b39ac58881883a77f15d694244cf24fa2b652efb546ad42afc38878bff7df5ba3b21dcaf4f8ba7821674f82697017a9dd6c6949ef7a12297955027f0fbce127ff00b54d9c5d4b29d426c98f120504d46cf482f6e96cc5a397237f688e6780f8d1a6cbf102ba5d2d96769ca3d9b229f94d363b2670a1b5069c469f8141114ce986288217fb2751259b2c45898d9e5bfb8c429be4545abf7510ccf2f5eea2d46da9693f0c54b8990aca4890f3f5ee41c570cba29199dac505d0265142e0f09845249547a1c8c648a09092945d0d7be3d80393c0b3c1d1b887253462bdfe688d0fee253012ef90d5f90e032774f8d1a2c8c9615331dd26af58b319630bb2da780405bae992b54816e4841a96e0e20cb863bd5931ee396e44e248027188b5c7e4f0ec6bfc7fe23c267762eb776eb022e2d54b5bd52ee146d4aa4d6e85edd15dcc60aaedd2ba79578d8e180eb6f23e8d4d91238081e5e021bdfe347ca7861ad91c7ac3011ab6c385854a75817f5d9eed393df17a9c60b698a96a753fd646748771bdef013e2683e280cac400aec9701291f5d4887ea647b66f0f4d4f48350dc661663e74fd26832356dc0cf25a1503de505724d2d854a9249b19153c883900a9924ff8bc33e263f3aa80cee8041ad10c8cb484aba1703ee3862dbd8487d1286fb130924c48dbfd45b21fd2d35f8468762a8459e9d95255f2411871d7d1bf883983f59cdbf74f6c60d1c2e95f9884a95d400ee5b3c4906a18b82fe34bf3a061136d136a8fa1a849feef6e8fc6bcd769dd50c0a298f5e10c188a493318ea0b6bd902f6984f0b1f9f531e55db2ef4283be2c4db97773dca09b22cda063c831f629a3466a46d2fc5306e1cd5c8587b953bd3d06eb69b5771c10bf07f611617da11760602654205ae02df1a31a601d1cdcc567f0e6cb0ad55543bcb9f0a040ada43df559b2b91664ffc6f8a2703f33ed33b4fd4ee7d3e72ef90189e5ae34ad9be44d12ac1cc0c26352e45f7f243d64c8262a237813a7e4f59e09299d9f717634a91e1a94a1e5c94f589234229c1e7d50c325851f1025fe3272d46fec667d5e5c241c88ac7e6d97403e4d30664953511202029d99487281a947b1bf9d38a3110a87a1f78d0b126691788dabd0d84c446cdbdf94558269c50dbed53842ba6612999b845587eb03a2280f784c386fb8a4d6d0624b11cda9eb04caa2ff0059fecf266986c89fccb760804833783d0539ed62e0df7aa40dec0d9207a78d2c091c07df06e845283736c05aaf9facb58622ade3a5323a2275354825be7d11bfcbab7baebcb3602189f15b07392874e231f3331cd2bc10e1ce4593c06598eafa8f0f15069f649f357a1ad588559f5945a5096f57bc035727e8e03f6c6c554a46ed5768072378cdd2db2b295f952a68c769f1281e2e32e638c5dff966f6aad7e7703259cb96f95185ed2f902f2408abfdf88f075c460bd216096861c8562f04f1c15b20e69dd66192d2261d5192ea5570277e2ff34f6ab8f1a676ef59d3d94b30b0095b4b6f350ec09078edd5e61e009cd15aa574b6aab77f004fe19a28baf191475b4f0b307a56b44555fc975e9e5eec1d2550da68cdef5236774b9e84035d4bcf5b6530bf7181654649aaa96fcab654c15826ccedb35611689dbe48f87f43383cf030c35a93356aebd0acec106d5d50fdb0073176f5dae9d30a667f6749a3458a7758e12a34186aac1109abca0a0ad685157819373d17626aa5c62edfa9cc330d4357af625b7e41c0313be2070d25293f618b034d459d99771c417c2e04824dd83bfba0c0cd1865055a1e89bedefae7901da2dcbbe5c1ec2676214be1005e9c55bd42d65f82fde5acd1eb959ef7905bc576660f8548a4e7c2f6453a524b9966190a3754b31f9fed7164210b2c92b69ff9daa0202e706f9115ab49b1442422161da6001b1f92f33fb85affeaf41598d25e5f6053dd47f8d7e5ffa15be2c319423f1030bc1bec0064909d95735eb116424a6c77044e56e9a6c1f783e7468b8a9612258b6ff6a5a4c02ebac808844aa1c7b086249cfcc0815f4892fcdb806df84f23559fb7d6d7341af0bfeedd25d161231365fd5963593d8e49f00a8a4d1e3c3641f5ee5d19b8c1f3bcd69301b0f9f2168ca0654a248e9f5ea8e7618e7dc69942129e16a0e1bd2eb5c8db31de92c7fd3c83662fa5107274bfe823b2893306402e6a71f77d28cbd3a2622d73526657c755e79e69862fb4c753a337a130fca5759e7f28e0b7db1ce9493efa7af401f5dc70ed29125c4aee35225aa396b304035687578ed98142b01c2bbce3e9485bb521f17c630a97cf302ee8ade1b525041a87259b4505b6a2e8c78ef79db5a5c7ec53533fc2e3fbeb054e98480e27f5ac87f72e4e24a2c638fb912318db7fd6140e8f400542f4c4340747a177e73d899fe43ccf44ef738857f9d1e2746b39f0245d526162abae1b7fc9c9e70d6e61cca3de89f174602e9847410044c8f80d8fe5217bf5492000509831f3e69a43044163dec479518999e19fdafe0871f88c21f7c052d8adafe6c19ee67d63457d3c4c4e4437f9d396e51931d85558a942d69f348cce2e70149b4a124ea380414c7363199fa4b475fdc2e3c55075004cb538d34e291f629fce7a5d5d10634131cc57816b490da900048f489b9198624246556e42a858d415a1c79c1dfec37697772c1ca2f19af2e1ac2f228ef9f78138669fb9cf05c7baa1780cbccc64cc9fa14e04312a25a764eca76ef3d7d8ee8cd9b2f80a6b73ac890fe71a4afa9d95d2b6a1707d3a405b65a4ba38cd81211847192e9baa21385eda1acd13d293ab0498c3e8e8e856d74f64d1d4a50577532490147ad1f742d744d87834acdbd6c78f9faf2e402faef625e37a4448cfdcdfa1504c818e68d231832c1237dde651a78865a571d4a50cfd070fd024ccdd93b5fe20ae1e4e41a89974488866c4928037c52bd3bd83a2d8ad676f7ad507fb18852f23bcea5dcf092c3f4a7ab8e59b4859c67826776668f208c112d6832f70474155ef5ee80b6c69af29c54bf9071596df1eaf53463367a34130799430c9ad3f3665f3eda858be0b9a5e6935d01275708141434b6f2c16e725e2cb9a100d47d09a2841982c8d851369a8279dc9c41d00a5c5fa70dadc64773daa8a3831347d50466401d688f9a4ab8c557666503b492386e9c08a27dbecc44fe2018a0aeb9d7f345c6870bec8429c09cad2d3b0d382f87ebf0efe9fe35c20777aad3fe5988e3be161e53df688e3e4156c1057e5af888996e9d3926884da021e851c1459510f89734a15244ffd9b3a265bf6c8dcf9f0f7e702504c296910b13d1774de0b3e51a693fd582e6f8d110ab7f302140015c208587bddc2007345b6436ff5d23db0ec2639f0c69aaab3b515d71a9f73b0db2f791d1b421e4940a23fbe7473c3a66a3ea87a0d95213ee40061116d0603f0e6dd734e05868dfaaf678ec0ce29cb690b33d885b3324b29b58ddfb93b97f30e379beb8c5da9c2d7367cae4ffeb9724d9f74c6c45a26cd189aa171389840a8ce73a1987a9255f0c6808b16f33d48c5557e07825ec04e3b053308b743c889c1058ff36e62a0cb9f6330e9bc2cf81fcc59368b373e6119b872e74ac1d92944d139177fc8e2aebba40db30cdd41c55b666bf40158b67b48a08822e3f6e2a0c4ae77cc11c1be29dea65003ae09185db3647d5a3a70d71f0f40dd5f237538ece20a110a62cf6a2b907a0d8b90f9a7cf4f669ab175b3dd04f31ad83e8fd60f4023d78f503b9d24614fdfe5c223a72f24cbe5f223dc7737d3aa05210894d78b1dd92f3d6cc1a8030b3aef13a63d4cc9e2d1ffba6d06397b562cf9a34a453b88859f10c2847831c83b10cc126c3a4bc151d68807e05eeb071e21cf408921a80c1f5ee10b26a75cdf90adeaa67410e0a54948efbc35628ebd9cfa24eb0a4c5b3456ff597fe1dfeef069b37f5ba96858b1a8c5a29fc7bbb72f302ed98b00a6cd5a7ae860625e2f0806a8ae75578e80086f17104da75c84a7181577edd6e29989dcb35a5f87202d24516b29a31903b16cb20e5f15a8d4414150ef52079471359a28fffb033b3d4edb036b08593f3204ae3c7a5d712ebcd573d6d948952e7d4ccd43a5aa885ff7f1025b519555d08120d7f2184d5372d6170d906d48000a72946cbc854d67d67a1d6885b1be507ac92a23343d4cb998e23790029f65ccb584254cc1d0605097d231855a3ea72cdfed19f7b01573263f26990eb9b7f2c94576ca0797f2fc95dd4c8ba954525856272192ff70c9a90b4936f8db86165a81e0cd1678e9f2bcc1597e62e8b2f34cd39b3dcd248626d7a98944fb96d5c2ddc779a8529e230fd96f09c2deea76b1af3e7c77f081b126a7b55868414220e935e6a63d7a291a8aafb75213de360d946571a95f062699730d9a5ef51669e23af014edc613426a26ed182980b7d94a61fd2104c1cc34b0a4b92b55455661e07afb5f5b51f99ecdec56c444bad73eb4592f8534ad3bd44f98352c2ad411aa228056000a2c687d4c8d3053d06f4017195efb4c987f8b88e0690e077f588f549d97468a506728b7e85c3a382600e6064627aead5c503d7f4a03667b9971874099a1a369a556643959e2ec799ff7dc7e5385830ad4756fe8a24ae075d8762bf5dc13bf076c96a73c4266390f07eb2039c21df9171eb0649b0449e88cfe426b12301fb0a7021d83f9c3fdbe42f67bb7c664613e9450d0a5caff4f5eadcf8ac641f9760b37ee609b32ecd92a8dbab4180ed25991b9eb849dfbaf638a8eee0e30e5de337482294c2e095dbb0cb1b6bfc43a991c7bbc226cd695508f89ba73296526cb34cfee1e98fc7f60d4bfb87e2d8f00a6c049e22c458c12e08c705158bd6e078cf1f8e6282ddadac0bc79f2a8340f4310004411204320702e68667d7a0bf32f86601fb14916c261c1d4b439a91108afe5c2206b0e2028228305426e8f4120aeaa0b8e2e02ea707386d6ac03bb2cf292037e4aa8844c355183db89ca0b2fde1f4a34ec7e0f1d78405fb82482b6b5c264ebf0c704381019a7cb336325b0ecdbbd4b75eeded28a2bd1393b634a010997807056f1b30ebc368b411b7cfd3c0eb039f8523e0cdcd14620ddc15248dc2b7f3d4750b0944adf3f3800ec442c8ed6690f4ae274e616de5ecca3a908d99f6613d1cd3e89c54e1d9aaadf388ef31c26987fad6ae855d4f3f513fe89bb858aa08581737ba96307fabf75e95705a30986c8df4ba6b4400085a8838451866af91580f960e45f516a3c2e9f4a077a0ba5d8e82282f99c9068917b17e6dbe7d78cf24d53394fc56320a8a29e67549c6f31f71dc1210c3f69017fcb06bb835d95a7fefded8f2268fc18bf13047bb01990b5ec560ab4a1a28423288dc43495542ba9d5676622e4ef7f836b0d5d15b97916838ca94e4c000387c870edbe8ba9459a960e49589178fdb806d1b15ff9087a5aed37621d4fab69bc5d8a32d020ab131c7464974c04d606a7950d1329bcd600fec317a13c6a9f04ccd3de5f3178f5ef24e1c68cd1d659a8b0b53d57fb71c7c793d7ec5eb1d799b6d2533723efb3d7a8aa91f310960c257d070a8465b65e6c267491a94dc55967ae6074f5a361f609ec91c077ab0571b6c14c434ce6623dfb00fa8879bf2143ad52aad5afd3be189ccd9dd507736a8b5e7565bf620477dc96979f9cdffbd6c265a5ab3f33db5676ee2a1eef60de5464f8ae9d61fc258d62e22f98e9d07c485f6834d2a8e1ede6a52710252aac887fb0530450e28057470e50a274b8dea05fe8cb0ebfbfe915231c9360111149d056ed1e52d1fcd27872c04be447bb3d846863657dea9cbd72aa285ec73459b254bda9590a58903a6496c10bda4fff88ab4828fcdc368872bfe7779b9debe48faad77428a5b2df9d042b4322e162664c9269d232cf738fd2145e0b605fe7bd07f5e9f0d6ba69f001059e550b79096ce116e07fe9966f80f3278ee439228666cb657ee5a72795b3ce972e216e1c15f5010c773b7ebfe220303a53084dee460c94fb796701268057e4bb5326a9084f30e3b026dc8e8445f20e9421a16737d6a524e4090fb91cfd14e8bc82a18eb84572302c153a6fe0027d6662dc296c3814c54cf98ad7c80687248821035bfdaef3df6ee6865cd72bd8ed28a8ace9b98d9874c66c9dd2d57195abf9121697ff91bf41edca4066ad58d02ae8acbe475a37dc850e7dc09173fe465f2fd6c0f5a04181191d7c483477447686ced22d0d05ea0ab69fecabf863cf4243b607b8b0bb38b96dbac9fc2d0b8e998d1851455e5f295fc5922f8e9ea80733391c8f75689aafee0bfb18df9c14671063e7ad2cc57093ba1b87ef04ff8554e355701e78bbe7352e553d240de10093f990d2b386f1aabe205dd64916c75d1155934a768d5a419df275e274ee208264bc51e64a1b2ebdecb4eeb444db5de490bd7bb52bb6ee3f64cb143fd64849664059bd1006b8bee221941de8f333990c1df5aed0f738c66424feb76a8d98153375c4e28c9d7620d67d7e650d39bd1ceeb2b98cc68edff8c6becfd4dd116db00ef8d473c5a45955505036805632f2f6c78e97c0534516db299b1a7de81e5fb6a12435a897ae20e0417004416d9811a173aaf145c766e1a2985fc06e26894b586a26060927db1f58f43c613cb4c4df91658a2d75c6cc36c5a329bfca2eb3b125841ceabd83ccde598911658dae9956a51e3c70ed8217f73e501e04e25282a5c59f78df0881d72bf153f9cfbb7ba70a12cc01105097e488573923b18733fe6cdf719c4af9cdfc3b86df2caf56ea17c632f2def79aa82210141db3ed2923308f2d8242e3f482a63637bdc6ce1e5c0153f951e27bb4bc077b0e3a8455a043a88a1c2d55c26f98dda7241dab9237923198cff1c6d079951b2033c4eae8bfdf3ef4ee7d20605790008540f3c4366bf098a88d33af0fe2c4872d39e4cc2b9beaef8efaa05f131a79059ec05e8919999a96cf5596e54297e9b1309971e0ccf79c32585a25cf1d5bdb54052bdebd2603247cdb65ee2fbb5cb354b87bbff3fd8ae2e7e8b559aa887d31ecb28d7f7729f4f9005e5d30aa2ffa3178d283c1426048b1905ff39018529d645106de0fc17b4a4b625170c50daea23deee0de8b7f6050f086139158c747cd7d648a5da9ab05b0c7ef26ba95942b33fdd74e7ca808e1921df58218410d295c497901d830d0e2799e04a38dd21be5ca212db177c5d34229cf6e2da59ab07213c2df89ca9095abd0f52dfb950d52cd19f03e2c6f8cc0b53c68e069528169382325df36016c3e2535bc0869d360ab37c40f80e195c1013b4f912e657af5b8dbec3025f0c80a2079073b786cad4d5124ce8fb63c9d8be59d9d2e1f1d3cfbd0fc7b704803b486701114be09bf19ba48a16d47af3c25498c8d7ca702653bfb31aacfc196fed116c598c9b195c6f37b6d644f07d0beeb0e36f7fde0926a9627f5ccb881ccab8d84e0d183c2833d6200764b3dd624cc55325e018ba61b56658afbd7c814072ac08ba88b4bd13d394b0dff569b4b68be80bb166a38d6944905d579f24734938a704fb27bc3955942402f082adbcfeb59e1401cf2b5ab5d31933eb1b20f97aa360dcf8e43841608d22dbf8165e5710523740c973f55d43cb759abbb279fdab78de709a5c71b2d87c6f13e7182e56ed6c8b748461f4e275c956f1f73ac4d7995b1754baddf538f0b23214815a0b1514af2c1f0c6bf015b65dca0a220f8f0356dcc8249487be7541ab3bb1667b44e01bec553ceed133fa29a8202a20d2f0dac8679ba00a00dfcf836216b75b4c6dd1f6abc718ef382947f939022aa70c436a451098f3268b31147e24e294b207d813845282eebd31968ce3c8be43a15461c9c7773e3421913abba79e96d14b992e0452f0d1f1595dc15b2785a2856028a7d2f43f84216a48e5167656a3635ab4bc46b1321a1218358a79c436b4f6cf97b5b6aa4c8d015dc76a650fa774f20a56f5b0abaf590182cf03642b0c2e512c7077f2a046b28537090b688688a00a5cccba355d18b969d6becf20c787d09cb908f5a492f3e46d61249899745b740a0a92131b654950cac71f5820b698400fe85b59c8c3150d4f3f9e875e758ea08b55c287a177a0e08f3f4a4fbdfb0a5a8b3480d0eefb16eb582b0af2a14a83a73887abc54b2273529d388bc8e1de1160fc42ad74edab7b0ab40a1fa8e6f67538d15eac7a385393102fba0aebeb689d311e43809aa7c1dad8739811b675d156173819ffe24d873efd4352fe7d48bdc9486a8df48c09c792643c6d530cfd76a0585d1db5921a6873c507d689e4d3a649f29fa99f1e8c7b7abbf17d05d1a0bdaccd4e49c544fef73715f008bcbb8f3fd20180348999466353d745342167828c7c042898aba0436d08b2c61e4a1d3f5894005b6af420ff13368b05a478a5c29821f1699a6f6dfe12972ed50ba9b15460c4db42546246eeb00d29930fd7dbc7fc5a40d9c27ce6dbfa477f1b62c4656be9b9b75321a65e3a27bfd81dc0a59be4f4fa3a21cb9cd246c9b7a41523781d15f206035ef87e71b4d8e15f501552f7cfe775c6e89494606d25c5967a3c1150805a6c29d272fb97a89db2ea47a3265b80566176b919381c16739cddceab3d177eeb90d8bbcb0461aee024c000a40ae444f173d9bf046921924e44dfe80216e75c00522f2109a686942afb3c86335d21fbc11e97a801772963255239ac132fdec5c8e7bc76287e007ff056bb9402ce1cd3cacb0acd866c3a26c6f3357e14be0aa38342dedf09ff25bfb0406daa077651960a00077b93479f176e49952aa2e579d2dbf807167a793e2263fbe9c3f14b03008baf20f48aa32e0072f4953b17c357cc5e34f4c0cd2bc19e00bfe318d8fe2255ff3336f48710da006114b102f48489e60c74ea502604043eb4f8c0239bd01fba9da2352479308d88df3afa48b3588dc61322368b83c0ba9f574f64c5cb18144cc4cc75c90f41ac1cb7ef532ef44044763f84c04f20a07e246ff2033655b52d33184a043558fd8f0a6d55b08f1b294f0ab868c81a3ea71758f129c25739a9b03270b412bd312b0262d1209ec3bc3ac8c9feccd4a247216ee2e6157a41f5057dac2115806b869a19a4094201a74baeb8b51904e034fffec37db30eeefcefc762e350af764e50afff0ec8ccf531c9219c26174020f751890679b25f86a53b47c2b5094da7c932914af50bb08e2f65253c5f4e35b47b574b16d5aa586413a7a7cf721fa5f08c16fca4210559ebb694a19d20842fbbe08ee3e49ee1b8792850cf824099fe531d382780fecd6dc54e441a6fadb23c3eb4b132fa3d045ab306d0e25255dc5803a1a3616ad835a155983dd55c062babdc9f35d3a6975b398df7e252b4291665dc3a77e112c8c64de32ddf5eaefe8ec77f4a688da6124de7738f32e62c73cad1e0275a7747d4b8fad380bb6112ccc1c98284055dabe58d8ba246b047215559f40020057e0f3562b3b537b095288470437faddf034e7c7090bbf2ef0e00778a19a56fc869a485c93ac03e60da46117270faf7d9092d10486fc8b6c316365121d887118d7e2c229a14076eda3a2ff1784ef5af1fde5f289a229a87fa4b18126db9e6c303bde7ad1b5c6a5c558755c310861993f02c631ba3858b82074a1e6209afda7908b4dd8a3f82f5d8dfc0022986aaae46d21a7d20b60e4f4976063c5dc0d0387aa79dd74879c4d8913de26ca2b31c9bee3e257be18cd1962ede3a2e4021a7d7596ab852d6865ad9c75a05ca28ab7e591fa6ec180cd1ceefc03e3619a49af0efdf3bb841953bba28aa774ff4b45de330dc5023f25219aec8462be6c15f46278795a7a612e3bf555addd197fe4dd81a014a7f2ffbc307de24f0748bb9368f09c1146c903f7cd3bbcefd321d0d3aff996ac0e74b904d31f0e57646c6cc2d084f320bf0b8cb0223797c82d984921188aa19822a38ebf41bcb0acad54d6c8a42a75895418b607c821bfff7e0ad978d7db16ebb6c6728a80d25bc893f14743d25dbdeacd50fe7e89032cd095c9af2d3d516c7ff7037d05959f289a5fafa803f7ac8f2ed7e7236f351749150a8e1761c63cbd4e822c1723fbd17a421810bd90b8b852d19e242ecc726a0d8100d649b376ef881bae0d008e0ba0def21296ed6fe5bc340514fb2116935c0350dbc450413dc3983d595c9c366b132f55951c7ad934aa7d8f457785e29abf8f9ae245247cdf5ddca10dd59098eecdee9a0deb6267577cf687c136c492fc9a880a333e62ce93f4dff249bc69a634e6bf8eb9fa7bdf4c69fbccf4c106a866b4fabdbd26fc5b3b030c3eda01bf432dc228f853ee57b86233e651b7ed50c3a46af16e5bfc0118533cbb1d73762071f5e332d31e23a51f6ee7ea27340e1168ab2018584f74168fca27c9b6b75cb96424fed60c53b7d0b6b6e5c9d3ede313004a05aad1fb06dff0c016e759cd7e8e97653290fa18cd9dfcc88d2fedbf80bdcbd1529521f1f622ccf1fbc51712071d42daab8255d2cef9cc1a4cd97ba6c0c93b1ca108bd2f2ca6deda97c9d6fe5f7228e12fedff33ea0bf2a30f4170bca7f5d8d93ff3c63fd338f519499c7ac5016ba9fb7610f07eb8d4339c201ca91f3de4d97787df367c7d8848f03effb989cb86dddd2e3f7f0e4f8e8e8e874681ce02c0d57b8df8e8103c21667f1f4f0f070f47ce27ce8747676344e1cf856b06feb076d37128b0776f3c251013bb8fb09d13565630803d5492ddbca8a010467a74cff46b88cd7d30f4fd992f3292088681506217de5d3f2ce98b3b62312667e2d7e51423effb73461ea329c2a9093a96eb7a42aa5893a60d00c155bcb78830d88006da07d97d18a7372708c179747d0214b40ab14dd74b352aff8deaabfe0893a21f8a33d649fd01e18508241621a94c4f434de37b7d4209dbc951b766443409cb3eaa1715c4c3ab827ef522bf4a42a104a70150ae8da96016229de5985997e9f6372bc93f8b83193643ce80a2d6e6b7260c8ef542a33146fff3117dcd46a16b09dea14132c9b3fcedb8fa45c7f18b65aa73df27d1f081dffc59836909fe55da6ebff9ac830bae9d3702dee48ba551a34c2165008704219b8b423c9bc8b18ae5388f185e49e87f25590ee0a94d489fa4a586561b838d54e1d8cbd80b2c1f8c0555a9aa4003278ea5b9a4c9f48e142f7a2e4f5b5ba172e377a0234206f5714df481eff8b0312f3fa3b2f911aec1752c640f02fcb3dbbce9442ab47df9cdf9a63eb23a3a5ab934e768c6216f54a4bb1e22ee7ef5f64d2bce5a6337077d8b8e9e31c1d0b9f267bfcb4420af61f77158c7c5ccfb6cb897067270ed54ce5eef40877149ad49a92a8118de6843f752f0782f767970de630e11b0d731a6bd793cb4cbc98a535c1fd520128b59c67081efbdaeb8a41313027c92ce1c6d55829ee896bb7b500676832c4d06b8390c87911ddd08e13b089682c4f791a1a66f1f45cb78ad997720dbc27d22cf9e3ba79ebded890abb199bdd286ef23ec2af0d4a76ad8d5480a5fe72dcb8d8ca56b15cf42ca0ef373155dd488276cec980d74587d372ede4e53883a544b8649d1849bd348e5a265583cc18f36095791af22891889460fc8ecf7a74309adead520f680589f3e4efd27a778e1931a7ccdea6d05a37497695a84206d115499dd629d16e44590f4d608321b6f313040ba07e7d363fdd04a608fdb0b8d26904fcf5b1222d5e0dd5e199173b1f6caf692b88c6ad29cb5cca81888e22296951f6f41e25e983f89d0fc3a98c4229573b47bd0674de31fae8cd9a912c969bf2d50f40f736f7c4e8b42203402c03d5ac11311fa4c4460f70f02785446b8e275018ad082b74828f3f71e8bc4a2d4204c5a0f717763fb00cf50888b9c191e0365985500893bc28d0dd93fbc805236cb517db2838876f4565cac7e2b761fa6314d10aa048b15dcb389982126fd511b7c11609961ff287239a844f1ab4e695056112636f68f2289771f308b340648ec205c940bfb3dd3afb5b6250a22c56d05a0e360611adcb8fbed316463822c95141360e85597cd9d717f20f5aaaf0ec100179e10752fa2471e1b0d18f2a4abdb7bfb4af72b2b8c6e40d718de1f0d3307a1bba5f6996d12c7baee2288a55fbe69cb21867e1a187cfd0fb58a471f554a48e997c5815402745191bfd1894855def027cd18c44decad90cbad09e43f2d173067592a10ce12255b205e580541ac580a2050feb05c40ce625778e2c7a6edfe92a56adc10675750d1e389a3d86443f2f37d96158a6505e42591a78669d2bc4cd78ebb9e177fb4a0d65c6c4b903f5af88c760887893b94baa949bbb19d0646c0fc349e03fa20c9673f5c7fd56b566c72e9e848cdc9167c1cfc8b83644f31da12042f488374afbb157281e39504ba8de2b8d1a583bdaf6355a806bf79861ef7cd853601fee296bb258b23b45c0170f75d390f8e9a31b7f566b4f1e68be6bd7ae55229f6d808196ed22320fc1e6c9d91c78a4d43887823e4144da617110ab8863e69618da374512a3d71eaae931a66eba757da03b6d5eb4e8a3b775838f0d30a252d9cc4828d02ffa096667e21d0984f4b44bb8daf4bb3497c2281e25e6a107bc081f7f890385af90ff059a30910693f77c09f94bd1972553ad3280e13984ffbb483e513fbaa8d203f806e42f05ba268ace9b2f7f46657bfb1abe6d6fa823d9ee116520c5c3813c68c464d9e923f1a0ef3e094dc38188e537e5c869a2c2033ec8bda9eb2d908fea3263350b6a0b448a29d02ecb15a8262b584082e66c71ce51681b9262b003539eb6e111eb377039ac8c16b82f83ff11ebe0b2103d4bc033acbe2925a2090329b0ea5089256debb18600017deff55c1888e5196e41dcac7d878d58bd68b2e348cc3a9341360a1cb2dfecbc243285049079773b2aa2487165250a1afec1dff8059221c33f5dc8703f5db979915b1ed6e7b4b29654a5206e008380926094b4fb4408d21a4196b9cc1c40b3658d04316361c914349d485a289d5cabbb1810aecd97082171b6ee058d8bbe1e18887252f57504a29754a29a592868ebd825d482b6432a3245c7c5029916195040b4ee2858c5225b97d5d949577a3a4058f2533e27ec0430c2f6ac4e890440ada04050d5d2c8993df0f461116cbfdcf3939438715c49882861b5451e44314143664b045962b3a50b981cb978406e344450627a921471228b8f3e9010ad29165cd8916170b5b2c065606dd0d77b9a7ee46124128a1f1029311153a300551012b9ece60c2a40627e8524555c3fd44772dd2e203ee723bacdc3527463cc93259d69cd4c082ee1304f16468808a8d29431a4390f9de94734e0e63168b2ec993e65da0c6994d14092152820a3b24e529bbe439ffda2a6bad95aabaf973ce39e79c33d69cb3f0b6ed02343a0a8e73c3167d23b36bc3167d39d1954911e2befb2129f785baf7c2a18e35c4855323d7b16cd8aaaffa51bc12b8dfbd9015ba5cd8a22190109914e8ed34608009fe5c78bb3a81f37caf95254b46a50696f38912fa8bcc9e5d4408f7add690fa5c8882bf841c29725f8a507dee85ea73d26bfd1b0e71a16847aee2fe98b08744bc2378fa18f21920656882bf0d67189cfdd60780b8bff786a34f50e67b825b6686d1249798a8b4d84f03be3f4e2ab97f04975ec47160be8b8c5eded697739c446188bff04531f708d306e550a3983f5bee32692ac7913e8284921fccef4099ed6a84279f0341662b3b28f499330e8bd3681215cdcc256107108df1451ea516364c417e05a114619186410ec4dddddd33151bd556b574ad52de1aba4be9524ae95d0f35ef08cbf760545660f99ef4f979864b59ba3765f9632d5a891f764a6b9dd5bdbbbb5d2b105c7486a32bff7c8bd2dd35d3a2abc57be7b546542778da6aebca08bb5ac2f34f209f8894f547978cc9ac0547569ef6fe075900f7fbc93fba4608d2d2b73294524a29ad952e84322c1cf02893655c322c9a2be89252887d1eb9cb3dcdc91da43c810b03bfa4f204994fc6be4b9998b103b9c6c4642c29476b2bb7808c35d33147a6e10932cec59333de0d9e0c404d428b52551c7198f592d603e31c73d8c6d98a60eee3b80f737822994547fc294729a5b488e3709c35c2fd46964bd995e6cb5f4ef0257b49780e2171a4ac37999380ec3f95640ecd3ea1984bcc23b283347bfddd7b9bca94edb64f705269d1fd69c045a61de56d75dfaf4ad5fd546a9c39c4d3524dd69a1ccda6713265afb41d94c8e374da41298f334b7618b7cd5bf65775d39babf932bfa76b3722454868d17d52366c1337ef4d26a60c2c7fca55bb5c3f58866bcc20a63ba5328fc3fbeed6181d9fe3f18e5007285bcc11ca80b2451c614c88c371d26042d9e24b18865c287e17f7396992a73cbb5944601a347d5aec69713ecdbca5f0973c679409a5451af0c8cd1987318bf55d9e83c12cecf45d4656cd40fe6a1aca7cd405380753193a32366b4f54904516590c993a4f787832430c93f797c97c502293d950a760b27b5f76c19efba3ec7ebdf6de7bc3b147b62393edf4dcdab5ca48a8f5fb0044f6a6444c6badb5d65a6bada5feededeedddedd3259d743cd0727b2b7cd8a57450ea699a58ca1d168b4a6997c0abe9d754e7777ffffda1330fee9912736b04e18e3f125d3b712c7ca179d3e2626a28c27110693c1645206bb81c16e6430d98c155c1957c993bf607e37ac5fed3077ffe9dedee1685fd6b01b99c36433304a61b0ae3d8982c1d2689bbdfa4fd9fc51467b66d397f5c87ac29b169d46e62f8382b2994a9d7efaeed46b3e0c21b3893167682ccd0d8dbd696be50d0d8cc6ded0dc882f69ba768546be9ae6e546439f34c160b5c8713ac7b91da3df212c5729c56fb17e37d3a264ad90b6781a7eadb625d812ec56451adbb2b62e355d8192aa3d45d96e5b727d3a9d3aa5b4fe4c7705c2051d6b913271420bd551aefd795722c7e99ea34d32a7bfd221e81079c44a79ac4660eeb073c251b9af1ae55a8d1a074fd5a3a55a8b922b945c95d6a092c7ca946bfdb13ae5b15ec9b5eb73d7ca27d488ca76ce1e51c85875b4832711e0b5695d32c7573f7552598bd6c3bed9b9a9d2b5edfbcbc29e1681de07e8e5778f048164937c79a59417a8c7a7c5f6f7e9019299f6008d361469770927d4dc5df4016a7f8e73eff6eef67eba3580f39c60b528d322d79ab37f504aa9ad284b1ebf16944129a5f467bbebe9cfb4e04aa62e7716fd19b25aa473be8c0c225b9cdf9986d239a369913e16dc851fa5c9942aedec744cbce9d9f1d991d2a7070604dbd9d9d1626767ba4ba6c5c744e0fe5186c3371de4fdfe39b99f99e35eda0abba1ac47fe2893bf1393b21e59d7b9cbf797f54829eb9137e2d3cc1f699ca66787c68746ca645226b381cb7626ab09fcd157894131d0f2551f0b70bf10912c231011229285088bc56211111a21cb084244848808f1d9011a01ed08922115b2c870c93b79d2b814e1c245852c2ccacac2a259bec8da408b1d590f8e2c6b4b466d5419f509a24142578408d92021fb3e8530c6582848c81521414233480868e7a667073c7e57829a84b608691a61cb9613aee0c6b236c58cdc4a70b0bbe353af4f50ad55a020ce27a81664837c806a403e4134bb4f500d7b38190932197d4a29a595da5ab3ac4d1943d62c17594f0f900f500f101050934f0f50a53d403bb6224d2474ac8413bc49daa8a43d4d4db52939c834944d9788a93ac256e07e4ff6146f76b89fdce47e9a30bfc7e7de9dbb93fdfa748c86eeeed3b333bfc787ce6eeef84c2e94dd9066a6caa481fb4758dee958145054943fb7b3b393eb9586edf874bf4f8341fd4041d3278806758cba7b904f8340b2d61f44bb7d82bae7c6bb84a99a0a2318ea199e40428b1e4081472a6b520a48884b91e0236bb2d68213d01f28cdcdb47f637bbeb5efb6ad6dbb7323bb997f236bebd5bb61373d1d9545a86f6e565cdc502b6eeb4dbffc527a9ffe7873df04fab29d9b09eecc9d8e71efceed8037f46615c4834c5f76b3e393bbe91d7a43223051a8c59b9b3064635418d93f0353a7df1f005cd0d16be3cddf38f51b96ac4d7122d370c43772aaa6619fde4fc187b7648df390a2d7fb1ead4f2b0f59e3efab054cca92356eebd75cfd5953060cdc55346d4a9bd2a63af22b05c3e9c369f7171efd68a8b20277efd1af7eab6945341aad05d70bd961376b74d96b50a4b2e3f4ccdf53555ac7c6e97d257b4b91fde78dbb8979058397474155e425145ba4451c87a65158855ea80adf3e0c78c42afda082dc81a3e8df170cb9f0a38eb1972985ca3c1207463ef10298d07f42a9619449253b4de388266620f560850653b3afe86bcee6c91160fa2e6b556859564b01955c93658d072cf928298fb0dad1d1dcc9352c6b29b065580aa072ff8d6ca73d9810fc019486ad07c819ed00533389040c49d4a085162b667d80a9999cd1afe1fbabc5dcfe82c2925c8a908a010a22a32448b3970c4d5e2841b2822b3998f5c3fa65fd474e59d68e96b20759d67850ca3004c81c8be4c29c49f1f7a8d0945198ab07fc593ec14485d2c0e2a4ac22055bfd3f9b60ab652d7a908faa0bee9b54a866e061a774d2a9c5f3a49c53ce091a991305afdd9d52a74e4731cbb9c59333a3bc626a093c6777af7ef07bee3f2fefd1283f20d04c6b5dbd67ed0ff7debd2c2c6ba6ea3d8e9b764a2359d37545aaeffb91df8fd784bd979717181898d5cfea076318fcb2c2313118638c5f66f63e8f7bf960561ef094b0b59155903ff3cb2a082b4ba72c5d29cbf724c7e99bef68bd7c37729c1e2fdf8f1c87871abe24cbf72432d6f2088917e1d07a4be338cd5fad7af90d15157b79f97d3b22a5d291261f91d23d0c3fd381539ffcd58e5fb2febb9ff7719eb4b693397478ab0f06945e7da12c5696543489d0e4928aa6dae1f8f09579e529d1fdcb0a040af33978c32b42926fb52a2eea251c5b4a6a1586aaf047930f4dd61cc963a5529364cdfc4e29cfaf51561044072e7c63e5604c3e00940dd4cbe900502fa7dcefe1f87270154472d9788cfc75e4afe6a1f110497dc114953b7081b22db29b0a0b3cea4ce94c71d1e9d231bbba3f6a512e25bb87f7a369a6e982af26591315f585634b4979e1d85bb6a8420f94aa9d69ca3d7aa0f43a563f3b92c00ec2e41e60ece6afb625b5188b12538a51892d71b12f624c2aef4737f21fbd686563b3b1f9eb36d562db70b15943672a05dd11a298528c8a8dcdc616360f6de4a1f9111eda6863cb4f3d8e2a3efa58ab9fef59ffe33db7e37bda2a15c1a98eb083ab0e44eac0d501541e13609400a222d94d36d585d545d6d47e38fdb8f2e3698d34b270c9ad33e5ae33b563945ba78beb4cf96b87b643b48364670917fe68ba5cd562f3540e7e2a3006153b8a418925c5a2c4a272c7b4c892dbc66663b3b9b5d8365b6cb85c1e5a0fd0872611d01ce8e5289b54114fd28e22a3265038b1083c4a289a685034cdccac7e54efa0ec3a0f069c597584471e5a770f3077a1f92b4f2d3135d59c5ad4e17906574637ca8279683c477890d8d83a76a53a76ebd8d38c2673b8efcf593a2a4aea25b4b1d9d8624a1e1126926d91dd2af776be47c4d5017c0358fd549da90e446a395dc902d562d41749f1d078888a5aec254d9076589a479335fd3a5c1a6747c7ec0e4bcb0e7a0722b59e05f2d076b4902cf1207f210f11016080c76a6b817887cd716a92bfe6377559caf3c73a95ab52b57505f81c6cd9671d9c52823377d3e76ae61770660cce0ce3a0cd15446a7d0722ed780c4e59335b2c50f607e36756cde7a0b491ea57bf8d4d4b55cadddbda5fbd037c0eb6b2e7e373b047ae2012eb3b56a803c4b0cf411eb982483b9e8608f81cdc91bd7f0f3cf060f56323d562775d17dadc6c00b0fad199c282471e1a4f51c7ec775667cad6eee67350267b1f7c0ee2c81544eae03b10c9f53dc059d1ccc85fddc1eb00897ee69a1d31913423ca4f394b86ca51d976673164a91f7f0d169725b6d40e401e1a8f114f131e281eac1cd0b31dcf439339ddf7e72e1db38ee3fafebc46e3c86eceba49268e60c90412d6926fd23728093062228ace148f91e388e0e4af9808502db68c89c085149b8705da9c215ffd2ed0460cf9ea17810b19b301438a0dd5311b5b8b67c89c1cdf6f6386ccc1f1fd3665c81c99efb7d922736e7cbf8d0c644eccf7db90217360bedf660c9983bfdfe666238693e38860b30943e6bc7cbf4d0c648e8d940d18365a6cbe9039aaefb7b1c91cce692a80e8b9ec10c166ac1f6d6e90fb6da6fa07ebfbbd0eacdc07a3acf590fb45b0394e526e25ca24f7631d723fcc0eb93fa649eebfe124f7cbf090fb71a420f7e738cafd7e83ace95f813e38f8e400d68c3ab03307fa2451813e4a401f1b644dff07fa2c91351d38730e50ca1a2422d8acc7e300918864b31e7f23a6c7d310e010b0720d917a8452da8cb2f643ee870136931201a3ac21e57e9f1ee407fd5eb52ff7b30bf43102f30d7d8a6437c78951e9573fd3ad76bb42cb42148b8a49e5fe310146363600c49498e09a8dad2e497f29d58a1c7cc91544723d758522d8fc558023b86f797c590b4c000dfc3105fee872731cd9edf6e306fe50fa41e5c712f883490df9634beeff3186749ae3fca8fd90fa285616fce34aee6e16a833a5d3e5c97174d638a263b50056bee18f261089e8673b7e003ed8d854dff7f320e17141e65c11456e41ea88e0347968224835ce8eef17c1b603d4e1d23f5ca8338505d699cafd3c349e233c441debbe5f6749e7a6c3d438390b0f4d76e3a1c96e3c34d98d8726bbf1d07868b7dc9f63b5801c7995c3a887116b05a455f53e8773182555b01a5d2879fea76ae2c46407a71cab1fee73ac40a0d995237b61168bc023e6a1e5289235fd9e8c056feeeaf7e08a72579a8dad16c99af99533ca75499e2f826dea24c9f3fb6dc8535e6173b04bc0e720e7e558f0dbf9e4eaf30c30138010ac74878e1def09934306c78d9897f93a403993574cc974095a7852cab7aaae7bd2e943ce316804bcd68e4d1c550e02827c81dc202cd84c7b7612327b3052f6a4b9e4f7e012a3d4698cf32aa814bffc752abab2ab504af1fb86545d48c9c05294a1d47b7ed3cbc0369cd5bb2fa53f0da51455217d0f544d1ffd3694dc163c43f028192c6941a41de5fc76a5942ecdaebbae8ecdb71dbd54aac0cf05e08dd5023adb104f1ffd3dc6c0b35dc0eade5eae01feb4abd5ebbaf7e7ee77e0f8dd776f95e5cf550837c80ad50209b242a6340c4f4a1d973fa60ff93d70c0f3e54e4eebf4944d1f370dabdff4bb5290c65d6c513e0dc39bedb4dacb75df8fe9ad6037b2a9a37af9393b53c79f0503e2ee5cb91e62e039237d0c4002d943e9a3fe5d01e9973eeadb15900e29d0d5159096a28b044ebeea7ba0b34a008267774d6b60c30f324a9e3e2229255cd00213962d567ed8804ad3549515b4918612307028b9414c14320c24162005e20a79662a6b7a3af72a9082dc03f9c89cfefb3f12a788bfeedfa6c8f7e58846bedfe03c226bee17b97f9ba07143bef7deff90f0bd61ff91a372bb49c01c87318bf53f8af7efcb2eadc57befabeebd5ed414b8e7644d81670addabc0e5fa3d9376711cec2f1725b53d2d3ea68229c8813b2db6867cbffa55c88214eebf10af4296bf5cccb415849ad00a926dc80199e70a48976dd80a624d6805b9f46f38634351059c6d8f8facf1e7c02021fd81ff057fe413f536059e593e0d9fcafc390e636fc29f7b5af49d9b2619e67fcd53c6eadf6bdd8e2ce40b845b7449c1415ed4f2e78445ccea21aa1f01fb6b7e6765c6859cbf561290516829729a714e933cd577efa940efdcf577fedd4f2445987bac1a721fe6afee67917984c8a72d20a1e026b9e3baee811aa7ebba19e42e0d0c85d3752f657d415ad1cc25753f89565fd71a72df7b2f64c1fbee2d2073ec773f8dbebbc0c4e1e4ab7b4c86a441ee5e4e1ce4ee2d089335dd5f905b8172f5803a5b85a2bf3a30f0d7a79fbd2267bad0843a55050499ef4b0ee62f0bd4e2bcadf9aef74c02c7f57b9112941a05535152ce2c37da3ed76e34dd6bb04759d65400254ffb4548a8214129f5377eed6e75c03dde7bcf5720dcf8af45ee73c24dce712fef5ccc120f1815fe9a78803d1e078eb9c48463464da82cf3890ad704e3b9c80dc78b3f1dbd78f8e1b7dce3781bf35173c9718a4cd09fc78c9a503138c0f9047a2ee2a424559e4bb03cbe70261d4928a1162d0fd89b2b106e3c91162deb8610a1c699f9c62f77a0b800cf1e7e8b560261abdf48ee709cb9c7355dae1a8a5c530552dddd1c08faac7e5a415ccfbd2b046ad1ba5ce1545af55fb0052434922d949723442fe1a47153c995d46211d6cf09de0960cb0333bbde487685e39ceefee272852dcfaed003b38ddc31dde2e1fa29c5834767cbe468a966edcf2cf62754c76e64fb33cafebc354ecfecf370d99f50665253b63fa3d86ed7632bd8f59e0b9c59c682941e0f86b221e0871f864346666008f397f52800be41cf5e95b32b7de1f19e79000094b9489116ed8feb79b86e3cfe171eb10ede3e8fb71fd4b177bd557900a503703e6569d13e0f7042d1eccbe3971df3fec6cbdc72ad56ae702685f30800a077c3b5c7f378155ab4cffd8e67bdab036bbf83702abdeb31112efb2d7012b5d80394e11162263c4a5b088221e72fac2446135d666028fdc502592ddadf01ba5a3ca1c5221deb97d7118e5cfe728423cedf8d970947569eb4699568ddb372f737c291158343989770e4f287846752388f6e98fb790486435813fcb9120ac087e110dda1ca2c0021e7ad0578165bb4efd9fa9d48bca99402cde38fd4c3a11f27889a9a85a15c35800b7b7c5ab42f37e0d1952dabdb4e250eb3fef377ccb3ed98ccd696d0a27d6b59d98601780fc0cbd510ec2fff3000e1908c428b191872e087e1d0e4d28515a219d8b1d934011471749b746c5e28d9ff897bd5fdee87bc1121efbba7341c1a323202a7114ae802057473ba4928a1ee5531df3d0d8586605ed525fb77608b767fd8cf428bca565dcc170eadc28eeb68d8724007f3aa6721e6bb1fc202ccab5ece46990456ffb5688b862a707e07e2bff146662f730907914c3834938c9692888c6e4cfca2227af9711ae5480a5b35147244c78e9733fce35c9a313f24a57ba7ec42307f231c9a377ff9c3c08443134a150edda6d39c509d5307a59a30fe18bc4960dfef1ff22d1b1751b8dce088469bbdfc8d972284a50879f58788b2bf9724e4fd8d70a8bdb0938cbc1f9bc8fbb18d929ce8d6c5896edd8505ee00af0ed076379513c97c43f9cbff87a408798f7f488a108ef77e6829fb13f1cc6e08bdbc3c0e5fc22199b09da0867a098639bbd14e312f67313ff6524cd8aa6f647695b23b4cd8aa466637a9cdf07ecebc7f79988781f9981782f998252118237f49a249fef2efc02437eabaa4ec2ca8c296d0eafb2e6c0d59fdd712d2854ed97f88ed82830c9c3871a2d166ee44d433d597a0fb2f6cd1d197b2ff2b0d2c6750504e4c321041051552aa90851274210d0825a244374ca32179a1dffcf5a34365ff3f821570a4294b29f24893a851f6a7469448a87befbb9b94fde56ca446ad2173a6fa3953bd77f1dbd7a2a1906b74e4ff31718db2ffa55d223b65bba87eb4b6dca221b5d9db5476ea94fd39902e81ef029633d56ce6912ee527da3b9a5845d1d44052d4c6155aa416505ea853a68e80a2a5861d98cc28a594d6262e274d2a426829e2861b6a30c56c64c9868a95c1694007275d6268620d25b6c899b125a22e8759b0f624acd90518aa18b54dcc982d576831831867dc3084bfe0491405c4683af24593194a58d080478c7ba0b07b1217c4061304e11484910aa4989d4515decb0f5996b47c6134c415ca7937133306366e70e249176870a183ec1734164f987278c08723564491c2052631c8995d8349239e7820820a23aafcd0b4e2d2031a388882140519947a962b9ea8407606961948614697267228810e554e70f3828c1f6c68dc2309ee6ac68c7ac203a594a2e1aa82f6cd93196262545ea085259858c152832c8aacc8018c0c3dd07040861033407aa21a65fade0d256c85c076e61b8e17c8e30a4fb429539713afa2c527ba8872a246132c5255dc80638244050c3960c122860d36c0422b7086143348b1c4195856209fb2a80590724617215e8032430c334a29a535a4b9f16c13cc2d9c88a12f7af71530640142882d32e032a5b1483086e8c209195ba2a88822c48cbea8048f323255065349ea1086961a4cc0061a33d8d25400712f184b7cb8a2c68464ca091e59b93db3648d91cc71d517f44676d1434729a55445abcaaaaeaae3e40d47ce8623ae3399c699341491393353265a5841152ac058524416d47b0a1d6e3612030c9ad8486a811456b4604629a5b40b27bdfb69d222429226d57c50c2d11769f712d6d2fa92da90c326c810cfc020e381163cca78a592d6ce72e62e3adad5ea5eb91a7ad005a597d65ec3cba9b53ecdf62f956fa5b554d65c5b2badadc609793ec9cc607a28ebac734a39d7e456c173665993c713e45396491465153a6349a50c1e69f6e73eea5efd29d741f660fa18c2d391e54f00645b6bf794c958e51eaabad166f9501ed731ea738a9efdd246ec9c31d802e32c6b3da031440f3148ea01ea4972b887a43cb2680f383e98bb0326e3d3c3512bc121d32c6b47502e8577a9e0f9922543d17bd2ee6e0864ba64c50a049bbb4cc3510459aaacc023cd6ea594e142d2bf321c0783c964ac2a9870dc0b18b358ffa24c96853890bf3c2ab5885a6badb5d67e9f8c9c9919a513104602d335eebdf7de7b67ecf784826f109915f2056ba625c817ecec150c95eda94555967ff1a62748266bf24942a99a9e9c3a36aabcc8355593ea4995434e61ae8ac0a36a29fb2f75ac86324cf7baca0644944fcea1ec8121161633957d52a42f5f142603922f6444e1cbd6dad003f74a2ad65a6badb5de77b9ce0b816296c0e34c06b114ebcb9c90e9a3803b9053c1b9ef3be3de7befbd37e85624f84ae5fbdebd773a1da275057aef9d5d027b912c9152a41c67836d6ba79d6dadb5ed817b635082ed4822456eb6041496b8fbeca8c982ec576494b5d65eee41ce5feecf42c2a3acf9dc00ddbf0f93ff4ad4092b1c3e40f6a7993efce50706f58ba2816d10cf8221b0610af7460e13748c99be7db74fed0d5983630399be8db9d1852307327d97914760c03d727d96b4a94f73df7fc67b72219e89e94006552056a9ae19b6ad9d76b6b5d6722f3b152800f9a23fd6c4643b236be813198732e57298aeec7fefbdf7de0bd42f7f595580ef8f33b3d219c20da714eb0a35049aef7dbf610a77e68e539ce9580d91b877592f28679c6d6b6dbbb5d6ce3a9003a5acd49d8894fa68f7e86ccdd9eeeeeeeeeeeeb3a9bfbbf7cf15a594ca10e9babf5f0abef033650da53f44de44c2f3633c7f1f94d5e28e9412c79b92284d8928ac4d093294a882e5f49d9b98d372b7a0ae81b3ac29b194c71d777724cbf21c78b4d6a75f80d9b1ce93334d2447a68f650ecdf44790395fa62f57344907fc79c4466a68f2440d0a0545507164365f741c1e183891318314254b40cde60bd69842a4650a1b5a6618005314288c28220a32c498c1e67b2190bf62ca32e4648dd7ab103a8f2b64ee8736a594ce96ddb4db5f8cf44c6166f72a08c3cfa4483a5b3e97a76bf694e9906210cb1c9f3c78e532e76ce69e5f573f5e5741463a1ab6808cd443d6095f962d1ac9465640da41cfdd861439e2ec5fe6642f2f704e39b9448eb1c111187e56dfbe9c197105bf6a032c75d480c79ff41b29082f1986d26e90f6a57235658da44c5985ce9e427697e168fe57ab5294e77320885966c053617ef5d084509a309f8626ac1a6082875f8bb706782a999ab2d98e689a4a70932c3f4a96a1e8044b28624014035a9eff29c15d965048a191e78772024a0f68ea9f0fc4ca8d006330c1128aa9a9ac9425145347f96609c51493cc6509c55418f921e0c9bfc1c6c465b45bb1a9bca02a2c2a6d924314d60abd3908c15969b24265c90a94a31c7ce858dc8783ca637284779cf15d255668c02b1a25898503961737b272843271e170680206479635261b66e2626693a31b1dd3e7048e2ecb1a1313c5a186cca7f4e12c6b4c471c531113110f2d39bad000ebc8b2864454d3e4b6d0a1246b4b8ca0515ec6053b90c8b2864411b659d6904041ce013482479a5559d690a0e12a53b71260593b22cb5a155be672c0ab283c74e0d0a3f54970b6807b4c1a96b52a4f72fe484399203c39f3d643213cb4c05124d1f22897f4286b525443eeef9f35c89c99fbef0fad50c55312a799fd0b0e9120074ab3eb843393981242a40415646be46e69db32d585ca76fa7b8872df21b5052cb32b040bcc589ad9ef69fb309ce309d3278fe37c565186e5f36bffacb09b16e5abc8c023b56e6df5e06816c164abd7cf831ab07c99bb852c6b4520c9348f34e37723caa791ca52d6c1511ba1d4d3cb520a004d33d0a47497f8c9c7f2658ad892251879fcba02c1dd7d4e2b93fded4b4f9461b9fdd91e98b962a96fbd002ee0d148a674f543a9c704b66da891b96f09036f9ce6c382b950464546387efe9eccf190ca1aebd6561b952c597e361a2a621ec5a8b6d567be3c5bcb37e7ec615b2a9ade94d2d456adada39b9ea0113a266b4648551a952d79946549c4924cc4960ea454965fbdfb65f00ff8f38ec4b928b8ddd0a57644152a33f93e12e79ff002ca852a5b2445cde4946b8626490697272172f004c40c6b787f91299567bc60065150aab480893472709fa25b4120e10213313859624412417c914653104250a1c1d65e4cc941a7ba49652175ab8571b484098824b2143106922442425a2c6a33e416336a6dd100420553b088e281872e6ea6688173a2b4c0640b173a38f961ead0177a3e4d410286292ba033e0a323538a5c58b01e7b1c96e1f0d7a2103670b3244a2a18f744868ba45f7249938e05840c99e338ee599ccd1cc7715c0d4cfd9235ee4abf640d59fa25fbd35032c3540d6098d190044651d1d1163114416581618684b602286450b2a45f32734a9af44be691c33e5388f0c771b521bc88212222c90af2086bc25d96b52447472e2c6cf0c69abbeccd4fd2b428ff956b720b48677f9b3b7b734e98ac45297ee720954daeaa1374d97c80cf60ad2033cf5419a84b7186524a296d77984c75a3474f77da7dd1e4eeee3d612dca27eaee1e1add17f80b8ebadc599352b74fd23d07ce53d1f608d0cb09c151039eef7f1b50bf9bee7d35046f8ba56bce564b5f528bc553554adbb097738ef321da7476aef59dbe676add86b27299edf4be4bd51c95a2b5be02e24dbb045fc7b26c4b82e9f9ad25a0b467dfa64ba2ec39ce23215ee06e68fa6568ecc8272212052b72c32073dd42427071032d56da30326256060da0dae09246134ab371d25ce3d7e35e5701f23893efcb36f2bdcf61c9f73e4b698c7cff8748caf7ebeae732c1aecc8932c7131eb93cad9c21dba7f68590ca96054bd9b2b89f4ab370e5712673f4f1925c8b4246a65296c8f49dc854a4345a76ef798de827187c944e144c5083c28b5b1b5c6081640a065048a0c48821ca1022849862835a932e8e90b62646c020858818b02c79141753c6a83d7c90f985ec5fbb16c413fa7d363300c3e33e9f3e80e092c320aa42c905509214311b613f9ed490840f2ec020098728679860871fa2f8e18b2c9aa64d6f71c49729a6f23019104bd8e4e6b82a3c39737ffab997b7611159df8625f8ebbe91d988bb49739c27721c51d6e2bddef5c2499b9836a9802a2ff00b388dbcf842d6b1ee3b598bf7650da22cf9409b5590ee5065a63a037b4fbfee5feede2bceb7a1f88ed31a626466c3f7d77d39bb8f9b70771fbcd96b39bbe94bbfe75685de0d1b9919998dd236d6dc9fcd28384efdfb45eefd2941bafa7eae54298430c283e0ef3dcd357b21acc57185ac0aa5c42d64e9ab9f36a105a4fb169059f37c23b97b2399ae80cc50caa290a8c46c18753d0a15d508000001054315003020100807c482c158288c3361f70114800c76a04c6c58990ac44990e3304c21630c2180000000010364668666db004c017a926d11c4130752b15a602131317c6e6a4bdd6fedbdd5d5e16cb797238b705e4ec4bf2adcdc0e0eae7e5e7490ca1e4c3dc2fda0b0a28590e934039af11f15557dab4adc8b9b003345e28d8e3af03ee2d02dde013db3a84b0ec17709c022b6e773912f663de7ccd62ecf8a48e21c907ee84d69d7f3263bf103d40285a56643ce28ea2b488998947048e7732ea6a833c311084b4ee013ebbac7a78e23838c13952dea58611784430c8aa0834c51a9f7fddd673d9a64cff4319bbca0022503f0f72e7c75ac58e032a7897f8c3f98e51519c54cc83f9de7ff6c55fb8badb0329ea7820032f9407be11de6d0c92af4868ff8273a2321aa14c5aed3630fae9421f1d729d7675c21500fead410d52de529413584a3ec0bef92257cfa60d8dd07cc3d0f48bddd7cb5e47599d650d364f6177669d8d7639fb59de51bdb6cfcf47a5f3fa5daba38e9627ae8c75138f24f3d4cdabde6e8eb7b1f26802aa06224f90113b5faef365f48b2a1fddf47ad771bf959cf960c909f8a4c1a6afb0e267ea2893ae4df3ad5b43a0a2d3171b5e71576dbc23e791ee7ae884c7ab35bf2ee467cd9ed31a2212ad5aa6b57636bab883d53eac9d91dcf306907d3d6cd678c22549617c50af3be4b66a5c29cf15328250ad7037d3613a78a83f4de9796dd4b36b353302dbc1500c040e77ad9565601c31d0a071a449edc868e57293a8e8dd58ecf66706ac10c6394d424cada709f7582bd228b2f43e2264fa399b43c7d4f4aadd21e3f22ac07f53a923dd0e303ba9cea8b6817e7e73a14ca4a26c6a875850386e9c840969d532704dd7b0a91b57f7db8e458cb8917a983a4ba1a9090df24b5dee710432ffd46b8f5676148313e05710998a075840e4cdc84ab881ee440a11f1bf8f8dba67046c517f0c2d9a6d005e24c58ab6ba5e9ee505e00bf930079c159bd33a293e57e9ef2203be3a7a970ce0008565cb9ed2597a5b215fb090b898aad0020af86bb4252117072469bb2ea4c19867126264c4787b7192f5ef6f100b71f451d3d08a2f43dca4acbd29b2bd614d1319ebb84879c855d46050e2c1219af6f0a60ee564769d75a910dc0e19c343600091a02913df1994fc869c58af395ed4231381366d87717f9b5d94b6ea8667e3bbd64d2ee654e35be1746c384f638c1bf66b152007b68c2df56078912faac660bb42b38c45e0f2d93053b3453cf38c54b41577fad46014c2f2f3057256068158a832359c26d65d04231a166360ee69685a434657240cd7f0b8d7f0ad286c3aebc1ec117092b44e93104f087293944a306d0c0f7421a4674d503754b02dcfd4f087ebf71bb2088a23358655741758743d972f39223fff673f510d516a1835877b2b61992d6af8b6fb7ca2bd55655ab098d19cca6b154826e89791a7ed8e274fe48b72ccc05a397e22a4f812fee25252d323fc3ed277932ec1cca62c2c1a4459498495217297183712ef00474981891374f0815fa8121afa132c745352e9952bd50495d334fed7a63745e2fb0337c4742146594cd34a77cb18603aee20502a81521fbb294d2f959881695ade4b3f414f99620e2c9473ec8da7bde60177c80aacf30699188ad9538d01aeb277e58eaa68ad0444257803313284b861bc6445c5cca2fe8553c85edd080677c6cc114af3011736bec038dfdfddac6bf5d6fb9132473818a6e29aa5749a8cd9d5932614ee8df262b091c226c7413d1cac9dd047499af9a478e6595522c8b060051bbc47f83d7adee0297f9a0de60e0b1de2b9d514eef9439bba12ebb5e704674ffabedbf20b14e6362755e693e586a2ca23a7f575c44ecacf5cd223e172536de5929f1206d38824743f5ffc1c3d5c776600c436d9ac9554de13d3be8e5d76b250616fc39bc71ab038503cfe5cf2890a72f1657717b04f41a390f6b9d204174762b546bf76a86c22b180deccc53e9a06bab72b474738b97685559b0ba0f2ef220f7a62ad65899a15fb0088cf0b8700674d40fe9b90298a0f07280a5f4aa2197b2c8ea1d33cfad4b1d3149f04740a9700aecb4298f5cb9fd80909fa2d78651d76bc083f97034d553d160c483f4b764b91500be3ce82b55265aeb202964cc3d6c6db02665c8d7164a2e78359a86dabcfce052448001b226979e0f19cd0c0eba44a73dce23ad60dff14ca36595dc08be939d67e90447beda2bd67098c4cd67e79a0e3c7080016800f14d38282d43e0c52587ebdc49cf86c3418efa90828df29b5be027de6949b63e3e7861465281c913539106a84b4d1ad2ab1168620a61693b38728dd6d8e0ca658a503b07054b1505f80ef73233528db049b149b8144336f2d8ff7a4c1b4739cdaef3eb0e4fa0005373620e21c209a49666f8bd516aa59f99f1369d8afe861212420ee38976b7c02af828851c3b07f15bb958a2ea513b0363785950fec0f4d2fa11de5726875cd9667b462dca1259f9938b18a081001852a1bab7266eeb1dcafe876220cdb089597606e299e28b2207151a2a46103f26727379f8c3e04ee65e4c03fda202d1432f0433ac62bba56b43c8c204bcb59cf38d94bd5d06bea452472cce743b002415c7e307453c671d5abca31f16b5f49c7b2276718bb969d5c8ea3d9652097fc53a48e56515ddca49ca0859e6de18fa96d5809896d7801f276336ccceca9406e949a83881a0522a6553b670982b629fe092ed4a78020bba267f3d1976d8332a6a34a711c618e9150d8e2503ff4793104245c6d83a694b7fa8cdd3677eb3f875bd93af00071c25e5d199c1d063a0c959a802b07eeca10ea1b9ade305f6662729faca631c883054a90cd28d88ede198bee83f7b094213015848894f395a8d77245faab9a6bb64b2944f92e5e711647adebbcc3188324480918b0a894f5fea43ee3cdbbd2edff521edb4a76c1c5ed2134c2e9d0891eb8e130d4a26729c2de5cf78c6bf9e3ebd8dd7b9bde8d333e772c264fd67a1ea8c044a80e021b2935af19f187b1a742b2ede3a46cd291e637fb75afc134c29e601b46feee37d71805d1c1f114a185ecc516ce52c15a6092294938354562558024350e93974693353788d82b114c9a40ffc49c54bd8d9f3be76f88fd22d86d3620f2468bdc4c1acfdecf0c5d7cd55358850d763c71b5f69e6f1041b1f93719e51e63b377fb80d6903491c0cb1c15e533578c39c8308a4e563c4ce89083fcac71ebeb16f6425f7dcc13d46a6aa425aaf0166eb0cb7ceabb482003ab5f5c7fdddf2cc2245b8882250ef40b554969da5eef77b35d06a72d7dc3d7bbe30fe0d21413979bfb24d6ab82eb578823c5cd35dcd6089fbfd20c71572d08b7e18f25b7fce1799ed72719e100e6c7efeff3a84edfbaa8782cfb2e7323d723aff626c518a1b57e08d2a06a72ac6c023be3473bcbd352a39e5329403ab710e917f84f2a6902530e002fac490be22e9c306873bc1a27d836d3685c8e6aab6acb39278789aae14c8cfc55232b00637c97c1b7be935af4eea83c318e7c24ffd5d0c5aad86943d393728fbe8c950320609110298e5e824bb697d49a8ecd67f2514f0644a9c83e619a8b0be15e01f0d2225386aafcaa6107bc8bd18a4c50248d1d93fd9803ee4ff6d83d8301be4b075e06c147dfc0397494e036e578be8f87e2560063978edc0bd0d10d4c45706e5d23388d3e34509b34bca40b3fa5b46402be816b8f51e0b08c94b3a903c6c8e6003e84cc41f43013f4cf1ee8cdf1d0fbc02d4035e3c23b207a4c05e1e384a719cf86328acf0a20f62cd001d94ca07ac1347884f6d969b0a033b3a54dc6f34f75871764f0066385ce2f876fab885800cf7cb5322ea5600341c142ba9080319bc4e5f6b29f789ebc4ce8b5b430a90e13c94c8c6a1b3ed14057bf9089475e539cb2b3aa925b7b8c341140a4c5b0ed350335a51db5d1262d59f9e928a8be51edafb581bc3e19a9ccb3f9ef0ed1cc78f2f5a6b4f2b385e0de044b106d1168e37190e2f6fddf75ed2cf8df1ad069195e9fdd41b7bbb40075185e892bc5a9f8e184a0275a14700de0acadf243116d8bdd5939a6855ed12f4ac4bfffdc71846e14159ff336a882f1e0ce1eb09135c2286191ab442e72f1350cca2d46b25df49d1c0f0d89759378cef2d2c99fba722b93c91a1f668a875de987fdfc2b6c0191091d30627281f3ab6c5d4def14eb50d49bd9e7ef7c5a45166810f3f166557195326008ce61d20bdafd271149cdd6d57ccb60cd6d7d533904c31e77d093f5ef31775fccb9945cebcce8b30dda364bdbe53c8befa3890365ced336991e817f8cc0d126a8aa869b936325f674313b7d4d1be411e13587dbcd3a54c23f09db4a6aac38fc5c9f022e67e7a6b49854a5f9c8bee1d178f6a9b2343d4edec70f245b98fbd30492c6d73054b119ae38ce26cf797806331f9c66ae6535117457e795ffba70ba4d78453bd7ea1bf9e7c02edd7fe6d170f94f77fcaee72d58c6ca1cdbba3e1938f745a402aee7c3be888ac871d91d90a324e9ff02087f53a34306065f8268792141b40ea7ff6c704080c8d444ba7ea4e00a1d908477902220e53980b2b5f1cb91849e63de2040e98f9c0f575146f0c5831c0fc5810c58d1c72034e2f975fb3c2f38ad01354c451cc46349e2157f80b1556533f441b2bb4777a00f5a8996924786f983d9d4b86a55c31a759080523907512a08071c645543093a21c1620d7fb0f0b2a1dd32db76e030cda55df62f92d40435890c7d95b4580baea0a73fd271d29bc9fe1e2c202fa0bbfdb29a3d643075108e5a7ca8fbd83968d7381b71e81aefc06852d3eb0dc777649974b6cce1fcdf1c87596f07f53f1cfa059ad22244b50594373331c4e16edb347d216f91212ea80a85213b5453adfde0b3c7c558e397416900e1e34954a69c1457db9fd841bf9b423f90f51c535d09eefb488938220897b6ef09b53b24ac6371a90d6666a9bf554ca7b104a354a6d92d40cf78175ef369df4f59b55711ad8da75cf388bf3a18332070d06f13009d016748f0f10de5aa10cb5bf3a28b897712995d9d24c4bcf8f8e1e8b79843aba9e5b35856194ccbf5bb1c2e1df20642b26b10f86daa5042493564ba741ed55d2910f58014cd33f7e39536c6f30f53c991503eb4e6e6656c05d25bd63dbcc112fc01e37db561b2d48136bf69ce350888513588dd10bc66e1f350f05082ad1b9bfccb57ea44fe0abb73932533d10b12e868e1353895114e25a5589c9d21ffa4547d62aa41053cce8b520af91188c1f86c11ddd952c6b4950d3d4366301177d2874a7be89032363151670d5e32be5d7f0c216dcd4b945793d579db606315aa73e8a2b40e888407677930f5cbaf20620f6db8bd23dbae14b5395922be99cd78d3c165772fa97dd79132b12fe97fd3c122b13be94edbc186a25543871cc3dc04d326168ae1eabe77dd6bfd5aeb429c189fb7267e0963a8876ad3967c42a99d850f4012a13a9b3767fcd8cff02bafa02ac5fbebcd17fde948abd72714d14d417271103577f5e17a92919ed54d847697fa429cbf75bf362746054cc11bdb12c99e0e30c9d642c165e1c4c7cd50fabe53349a76cd801d4b47a7b4e880b4135fde32db5a2af80f9275b688828d31218924a1130899d3df1d1a019cc617be01f138384e3f53077f9b2dd349236550228e196bb32f09fa2bb62971c86108a8f03d88e6218a32045de85c295d2eca570ba5889c24e58e0069f61fe7c71df9cb84f20cfe4490f55c23f7b9b5be86b14bb4e9fead0d7fd6aa5913f299bd0aff7d1162232222100156d4bef93eb971fce8da9c86e85403018ca543470ce1070c0f1903a4b4f81e3a4832eec2ca35e24d0af5ebe2c44445e67c9fb3376e57be0b019bbf932c7d1f807a98cccc7ea40799157291ad3adf5614fb3241fcbf774df85266827242395e5c6c7db16e43fe3ed1f0c8f45c4c9638a05dac9fc592f75b0b6616967b08e7ae212f5ed86e30eb165fe46106da8e89ca9b6735e87c0b56d0ddf3472360e6979f4efdad4c825a83e42b51cc102af56ae669461bbbe8b8ce62b7025120a5cb89ec00968da1ee3b33795bfd3b62331675de40d647969f70c20be0cb8ef85a6e395b46da489e2925a0048a90931c944425fba9f5ba5b0553cdae47c347cb6ff9f70c84a139a250b20dd13896fa3ab0acfa4a63620dce2038ef53b7588789e437af9334ce62318f71bdb3e5320cf1fdaeb1b4409209a94f5e845e6e2466e787a43653a45e699da00b24270d41c9443053a7ece2c445e806b734a508a9ef9830773401f87466925e23d4c8f8a426206b12b79e9d168a9feb96ca9eb2008cd67151eb7829a300a35eca2e457ebe5310f0099df39f5af407a1c728e5de5fb5d8aa1e39e1017225f1fb354f3283539a7637bb33f1b86242f26ba14f466dc3e958bc5eba056b83862d7320ea8b8f8275d3ffda1c2f393e63f51cab4403248b429984f3ce9242fb063ba9abe9a537481762ad615d85a916a7424bcf2144050bbec91a74010a490a0efc936508ecd3879409fa895e1c9a74ac04f92c5880622282594d5dbe3785adcb9d5aae6ccd484b05968b09be03f194270b6ec1ad91383150c3b32012d671f45eeee6cf5159cdd75b31566988ea1dcf71ff5899a463e18a491101d0583d9e0c8cf25ccba4a870c3986e40f3c5a6a9db7a4e0bdb606afe3b54278040bf64eff4df3bcffddb87c8e96913529a90928db9500f2078ef239c0784d8b098a4c107e512755aad461fa556f9479f6858ec1e5c3b543fb2ed84e7d3166f267897585f49a82d1ceee6a0098e1b641bd41696ec4769d54b20ec66946e3422b4cc84164ed17d80c2b1eff88c2e6c9119c5e21c79bf44d10a10c41f087b334b1cd184b98803b46118046dd78e73249215983105474b3dc4808ae4664ea041ccefea314ba107e256605c3251cf006943dbf3cd83d74ab670d73f682f3d06d29258d1191f8e27c904689c3e03598c205d2cf4e629c6833cb235d38b69c6638e43a5b23369ddbc179d99257127c096e2d0e40befd408fcc9e3ef4f59b1d0d8d304c91bd646c83645cb80638cfb133dbcd30457ca4a20794e0e760f0a90742324bd972d91d3d010920d4ed481f89fa033cee09f2cca7b121794e9683662c25910e517ce90488731969ce3039a41d5b3da47e89b8c3fbb675e263befda9874caf3e889128cedfa97f869f6520c36c533cb88928084452ecb5c9d6d96ea5a19539757ab7b6e4d6a2625a7fb33eeccee8ef73352984b58df8de3097082b3b3d6c17ad3a880928b12d579e9dd84b6ca7a3698784d2b97bcac69df9af3f10d9c0313658530f734f944befc9c36270f3118dac9d5c58345120bef2f4555e63ccee7cc2649b5009a4545197a357ae0d676b5866a17cb9deb94ddafa9da32ffc806d85a23478a39744bdf654d5d1f22bbc89e39cb1782ad4b7228d34dc3ea823f2f0c8348f7e69d1498071936121ef6b5c8fa5c65d36aa31800cb83cbdb90f361bff8f2c2f77723d5da47eadb10c94e495277f6b1ba6e7543a89b1eb92cce5cf205e16cab4d2ca73a2ca06b9f57d3f01ba3148373f2ac061d7436e536fbdd67760f8543c511a5a20c87a920e491205c80f51b9828bcc11b404b12f3a1c2ab6dfb7bdcb12e2afd9f7fc7bc34c1b4c6a4e6a2a0f0c214921e296273ab2bc0fab0c1f2a927b543599d9fc09f2d5eb966df51d3de3daf8db64c652e7323d8256e801f42b835347751c98bf84c0b5081d06d49756c5243a5a0f70c9b78d7e987a5e4953646c66198d82a77b5c056ab3fc5c8bfe0f36560fc09415c87ec2aaa6367d44bb15e0d1729a0eef1849e498a34d633a48ca79d0aad828ced8dc1272a4a26bb595166bee70787aa1683a8596f947d6ce811719c964f5a1d7d3d501879e606c64de0027af56ed5b280422ce1b7c3caba3606ff7da694858a767c166d41a090d808c6c4403ca4e684269decf29fd835f4ef1a2b3fb68f7392c9e34463d458b26ae4a499a5305ae680245dd638d9463b440f84cf8027d0d21642ceeb8a5ea662c406e2a1abd725f9fd7d666270b266b2f52babd7f71502a331f4541d7f146f5ecb63132261e912fc5d8a6c81054d8d1871ffe0fe9e42c0c749adbc8ecd0c852d749731bfdd2d454508bf17f54eeb41491e70320ba5c5ecc46270f469e39c1e6aef61eaa31ad082e570d9cc926c154bca27e101c8ed568ef07901fec56adb450252acbd1e42b81131e17698e88ae4f917b490f70326df19f170ea0381f4784b939961a67f7f7e88eb995b6651912d08857d964ea12f3c2ef5be033ccb2c4bf219225f7ee86ba128891ed51608996a512e45f6e8ed51d05294d5cb6e4b425620e3a76a90e550a086d762f8bd2f625682e777843c857b7f0328f800d320a24b7d3cd3f5cb24e0be57398484e445ab736c8e77433f5a6708226e4571903d1577139fb30ae5be3c717a1803a97f0455f1c0560eda7586e64fbf1522e15b4914ccb7fa9a8292894a9dba89fc72cbd88e19c0e39e425773d523f9062f9299ac7ab0f8ad6fe80805ceeebf15fd98a724f856ff0e7c0df547dd6b1c254f08adea0770665237d623af3a4bcacb08ff41756cca398cf7099cd512a7911db71788c3bcd1065bab756215bf19b16cf6d12d1de55a041a82d85b41afbd71f984f6cde1251b3683df9abc3a0f8bb925e6779b1190779c0568578c7f42cf1ecd889137369df11693abb9f8479c98645a7156db65b8594ed4d5aacbaf5a7ce113b97818e89e3b07dc2e3b89a25406eac687c7ac6766b9fca2bb191bd8e94887d72f0f2401b0834d01439d535828ea8562f98158e8ab14d8ec7e8c46f149d311c8077ccb7ecfbb994f797bf6158c6aa6dcb89e79b15bc0f102b18ab8424f56517615047594fcdb0740d50e18c398091a71f0cfab3a82d2e927554d75cc6e8e1c659543cca5d97908c08f517da2ea209497db7ec357e4ce51fd4c5896bcb1a1e3b6c4e1c053033aad76f8839541d9dc63cf2c4488c59f519f74745e5c7ac9b4452bbda09d5d8feb2d9fdb47461e5418086dc782e249d6f2a0678ebc05760d85dda602299e861afaf85063503a1447ffaa7d95be2ab3ccdc4510be89694ec662826f9cbc0740824d01c172188cface96c3d3539ff1f68e23145e00d53419f28fe041d15f76439bd0189f905b74b13179eceaa38cd88ae9af053d3619408677580e76bbc1bdf3bc7f50d64b7986bbbb1693b06ed7ca912b26d7767ef29e7e6640eb4aea4dc52576b6ac35da3601e7d91a0802428c1dff964cfd7ec4dea86bb76246c68e4ebe88cb621bc276c222034f47ea94c9f034abda214e223f2b14761d360b9bfc2d601ba2b79afc2b8a4a5bc4a38ca3b43a7f2bd65c5acec466491d7a6e48cd4bc968c118933aef80cf91a494df6a63c18f9391b182795c1e63486699943d26208898cbcfa3319b3839dad941d74e65b486f6ca87e46340b8068a994a6eafc66ff4038eff76c455523790438fc9cd904dc2a98d4423c375ee3ca35f0cdad3061af257fad6e753822e574ff35d70e315d9c9c1907dedcb1a582afb03d7c047b610f286a53972e5491904c096534645375343a0389d1c269e581dd1b9e62e2f4b36ef6c11d2be23b2262687252e0a02cb82ced1b483e3f729de7a6945b82a4db702bd8c865bf32c79f62cb40f67b2ec79e56904c02a7701320dda59794f164560b9a88dd8cebb55ec930e83b918d0886450c2d713ca464aba4495feeca9fa12b06967512dfabaa03549e0d42acfa4b36e5e12a888552a0bdad5de48feafe3790dc898745b67e88239983892881b711d5a0ae2a35c3b7748087d670e187c0c3b4de8691d82560a11d3ea96a4512352fafccf99e4df9885efd888719b8dfcd3111cc41dad3328eac01e1d9510457e6e1c8354e57c70f68024b30e4201be2aae72c3a7cc5f589bf80f269e32555db36e2d5170e10559f33a4779b7e67ea4f3dcb5a5b743921878d6118324ea090295b22d151d9ea4215dc55eeb0855186b7667c8b9326c842b6986a88d868f89052d84666386810829456e3a4b2e54e79c17ac5e1e3a46eef7748c97bcd44a224f6b2ff5cc9734ebb59f18f03ed87eb7942dfdc78570f9c6d030033c194e35ca2360614d624348999e252882db7744d8e89a98243838249d8fe00f31359cb2abf863c9eae258629744c1ee4ecf991ac82f8525d702ecb8d848558dc87f9f1bb85e2250d72f7fd72cfe5d6da74ba9aac906eb921b2c539158db0a0fa25805421cf4d99bdca1f4908f694dfe0253a76e94b0ebbea1a6bc5e44e2afb5e13c6566e19781d91ac399bc8ad63d2dd7798e6103a13c8742660698206e5547eebabc9b4dce8abd711be7b70f1b84611f984de9532aa0a9a6c4c1946f47c03da52ab633ceefcf07647f5afd6302b58bc5868aa3dfde13029b5dbcec2afdf06c29331da27032b7fdeaa106ae2887c70bd1b4290118e03f3efcc413e1453d5df5c1b84107b4d72038089e5794b5221f621b578b0b8e7f01601ac6ca28f9b5a0fee3c6e1304a0433bccb8ef1efe16609cbf93d8bd40a488e47defdc8eaba21a35ee0bc5a899d9d912a8345d27277b418b6dcf8a1741bac48643295c6391598ad564c59119a4b1f80c3d467b2613cb7e86d784999fc0da153ffbb4831f17ef4162baa108c144d5a01b2aabc67aec461e5b5cfdc56fec1b99f43ba332aa8fbf8fafb4eb0e3898d675409863dceac1d5f33bc1a83be68e6c192d634095c99e8606c643c620e391d7c196e1761b6be9ccdb4e47215ba8239254f65d298bce19d43306fa3d272f185bb6e364cabb81ed3be7b64fef67ef2a0ea9758e234c4e81ef7aea7c6fee2a30f6f1816b097c715f0db730c56bdf15f97caad7982c78190c2552137613b9432f38507993cf7d3a5bc0a9c41219222beaf63dd5f7da41cf6f0bc73b4757273e8b211972df4aa4c5203632b7e69b695d2faf7982d7f6f8a5845741c1c6ca70785f894c6e8a949897b1b1a762513881b50c2678fcf15e0beb343c3495627adffd1eba117babe4c7d63825510684f9d79c635c336ac42f76e936ba6263f32283fc2fbf0f504544d92d7b1b6a427d19f519478bf2c401a874bb7961b07c75f928d5aaf75476404d5da4165c3cd0c9be231399b44cd98f88a7d2ab508a4666df9a0a53ec189603a7f03da9b142194317ffeebca9e906aa393c2d60c1cdab03e964d5eace908905a7401d42ecc7c45ff3b2679e852226a5d7b6ee28b194f1a14c2cb893a4afdf44b500aaea1ddfffe18be797da81152306a74d94653428393cb9ce5934e492c7c75ea67435c358005d3ca93bface5466aff3c644504912da5c0772d03699ec4b557f1e0ad508caa74a6b83c940fb7af867d6a5ec86b7a4a86c0f4a22a389b931d704374dd6097cb4019ef28c40b10f2bca6067ab4def0450392794d6a8009dd75dc430ddf88e73720cf200d45f94a16faa6fbf9cdaec792111d09e85a65ed648eccf6592c33b421117e975764cd777704c37ee3caab3059ed1832bbd74d809d100c7e34eba8435831203b5c67373c19c60e33d05cb31942aa3aa69fff807a0d84c628a8c563f5a8d063ea688b896e71e6d2167244f37f2f90242b73833a227499a2d6d48bbc24d422474e4ce59711b708f66ac7248a793ed7a6a8d0e066f2be894d3155bb842108789ce7c6f4de5804dda8636c8ee73e9dddadea52a638403fa188f602b8b49a4020c442822548ec40d1f24dce0084013843132483245c8ea1b8e1abea3acfdb4f596a230e0ba9bd61b106282c007c4121c1433d5c599540d7043a437dc23807ce743a6a10e53e2416a1d49dc3c12b46e78c0356a84925f2f0a1f31a67bbb8cbfe6383c24472c0a95ca959ff3452f400792d954fd963ef994a8e5edf3a19e6c725cc5671ed411462befc9df16d61360a847791b60b0f9c5fa631fa14e83145d048aa3651ea9fdd463480204837ba53afc8bb628eb5bfa084021a12e6272087560d860f68c2ba3cc560644a407f25ee5cec10f7a4b96110e5a6d9b5d209995113a6a207f74b71ae442e413b8553372aeb9bc2d2212d2bd430b6cd61fcdb6f6af1086e3c5d9740524d216f0e772dd8eec979c1908b60d8d89319436b3871bf4741e231972ebe415ec1b0c06a916f8a1ac5c12169013bbc211afb833c18d72da16e8024880f96814252323439fcf10c749c0bd8f0f6cddfc8c95c554ff5889e33d480a54348f342c9f3bd646af3b94fb4c6a940be80a6b6ed8e53c11c3fe47cfa266b4d94e4a593614224936cea2b44dd5c08fd856db953e76a160392063b3dd7f53ddc9a92dcf4cd524bbb76ebc33a1b49c915532e630f949779d6b9c64c2f24e3c4f391d144f0f862c6e019e1bd72c843913631d022c3017f24c3dd9590ce7b3f7bc80ce1a9542efa4177493aeca3dd4baaa715c93f9db44ca5485c0b4945f461610d05d71ed998cde88ec42c4acb29529a90147a94d29450cd13ac8118db6038cf787bd1f40f50930d0268b0b6e7b48eb64719c9f6e426e2c5181891a6dc9fee4f2695a5a1813d17b713f32844b881355a24cc6793b018c5af6761540c175cbf0d514645283c6084de7055297fde1d3c0714b945f310b5d807e8b4ad3c901b4c19aa611438653a13b213152311129cbbae62b5e475f6856411b1a25cd9d20490bcc4c3da902e7fb163c0888a03d20eef58729c1d83091d2fc474ffcb8cdc32a85e32c22b20f7cd4496f109957b86d90aa6e3f5c104542223d17c24ece85ba36737dfdcf174342c1c857f5d2b69049abc20e1ca2f1a0529b119e7c5dab39fb55af2fdc41d1cc675707bb11870554051fd91b6119519988da4974a9b937109f0282ce2ecb8b8d12e8f90035bd4a428f511a85a3328baccc04a4dd78d13f27ac271f333786dbb83c4a2e480a5ae31d2141ca34905f7693eafbd601055642fbdbf3f8cf69895760db1ddf6e200571e38b0b5e8dce929eef322787033fa7b599a7fcf40c174d33c06042b141198406b88252188916cda6998573daad66dea074f9fc3443c659cad55d1f06323220462a1cc493b214a59cefdabb40e4bb7bbafa53636b82e6323d5956463f5d0c780dd19c86b1753435bf5deac40857f457ba0d713701596919aa32b5273bff40aaa89a72a9f192f588c1ae36e7f801ac3328bfaacd29f7c398a6eb9bef6a2b90dc6df9d51ed25b64d2cce305966a5027f1408c50538c6eab5329f0ee810a2ca86364ae694f01d1e950b4a340c33deda96b9d655c8c2ea8104a2a80c49d464a0d4c77231d0bdc4f875da6a64b3d6b1e92ce5de23d7247e55cbc18cfd73d0ce830d06b0cf8a851bb7dc4cc1a2124b79dcf9fa261c03fc41990de3964b96fad16452c29e3c86b29e8459f3acb1b4c0e4441f588e5470fada387439860661c977fa964ba9373ec8173f98f0eaa34e86f40d90574a009320caf809218de809f810232bd73de46e9b17b8670527629a49deaf5a70859ab525c7a99fa53880f45d5e0be5c8011cb001d6012e0de61c110d3462cb1955c0c906517f40c94cefa5518b4ee9b0c39dfb0ba9217f0b3882611644dbb1fb668fb5cbe7b1a97243da5c471f05ef44d1f17bc00c821391caa382f8ba839d3adb1042c69226425300eaa06236d29a2ffa5b314a995a747839279a2508f22a085fa6de4c9fe55e10f4a2e0af2b84ad6a39aa3d9e18bd4891c1ad123135b07a568ec84171f366875cfca3e2a76a48dc5c4953837bfb583eba42984cbe7108ca4c615701d6b8686732a2ed93188b4c78ae5e52af89c517a0ca58f2dad9551169917c4ecc6ba1237ec62065f1a11be2bf16963046f7bc7721f0ac1c6a3bf95c7e33cc7a3ed8c9a91fc54c957cf29333336e9bc5541f3fa4ecab2806ae5a9f2411d7a1f29acce32b80382606bc7b83aa9f2e114d162fa62b997cc150c938453b027872da5824d66c5d399c86b0576b2d2634740a8c83a48255e6307e2ee049d6621f98f809a5bbd3c5897106fa89236ad9b0909a07fc27592fdac3d437ba141d7694c60524ff51469d5b30ed45331dcc791a49cc8fb48590e3e64c88ac1d008586df68bba7eb5bfb23d4428ace187ffc9096b2ad89358e2d051cfbac4a04f9c1d31088dd4004d816601a0de651f85bd6c04d5947229d147707eefc0a6a5d7464497425696929a7aa509b0098884d45f6032c25cec2c8f69469f9cfc1d1206e39caa91d6409fa82a66cdec0de0745c83560a8db3b14c81ed05be2532b965b5b39a9ed917da9682b5563a79261f199425fdf392954c00bae73dea455576ceb5f0ea0ff9ee38e6617a11333e20617111bbc4b428d5373681362f5c7195a241a5f100147544ca3c5ea5e3bb0477e52a82c708f9cfc6d7ff8cff1c634254091b935c8617a80e28e686dfdaa7c0563aec8e8b1ba9c2c43820ef303372c745a19203fe44dbde40de7caf7fb598a332da1604dd35e9d75e7dbe9994f2d5296f6e3ed1505b5f21a79c6ded74461dfcd8a14c7076932d2aacac939436395a6f70627eef555c2fc87ed38135fea8beb35670a203e6a187354a7fe69fdc7f9a96bf4c49628fa0e15a9a1b0b0310f90a49b9faf3022b9c5d9c1dbdf88954a2161fbbb1238c51f765ddb0eba0d8cc56ae8ba26689f17a28f301acde996de70161bfd4ccc64a1f8b63adf66add88d6108822dcdab373eed0eccbe4d0082eb8d2e9cd9ba9d7a9fbfd56b69795257dc018965d1ab6ab03071dec5e1bbfb55500529ae3f87fe237e9dd2831c98aa4c595460437db5165066d6523e3375d8fca4ab9159e1eb003b665e6808ef863223cb168f706209c85ef362bef9000388bba851d48eaf6fb2bf5d7b2591214df8f7a0a10158c7925fb783df4a598d5ee7d146ffe9bd284aa433177776e0b759f005bc92a7b63771955ef5ab91363af29efbb7cbf3c642c12937b464e0898c1a64971d60aa17e714bd048094047c8d6ff4d939502ccfa4c51e875cc59ec35096d944d4233d3721b8318f96f14a72ff0612faa2d0c16e310b38e9abaffdb8fcdb17e75f28280f58ff92ea14e521e2f9d4e960a8617e38bd075a2c2899f3f088b255942b7a6190f602fa39333eedc702bb2403f930b21bcadd9b4f113248dddb63989634091df8852b2af73674c018819d333602617a34c33dcae5a28127e8881856f2606cc1fc3931e5cd83ca29b023226876736a5e065dda5ed12f7e7fc0cb2384414000c8699ff65f20781c65c44425318999c9a5a51662182e9bd74685e0fb28465059e0a1a8edf90c94f7bc42a1296a765bb0a4ec7e003ca59fc65a7f564615f920090e8aea3ed76cfca75994ab71505ad9b8e2891fbba6728c5c7618165941936f85ef0ca2989c5c632ff2d6154b4d142c3dc5241e7ebcf7fbe9567e008a8c0cd2bc99d1460d3985e853d31524c178df6faf452f7df013d87fc63b62f268af988c5db1e887141a081c5c66227c683d324acbc1809f221dc33aff29c8ccf94b6a20012378cc5e640260c4ad49ee6e8a0c5e152715a8148f790d8c5a10006eb8927257aa6c08d0b550524e0fd530a59cff0c37ee8054611cf6330a6381ac3d67a30943376e6c1eebf69b783d5636713bb02ce3975a63c253ee26669e139e9b76ef6765ebdebf1c84b32bbe6fd990f0318804993057d2a4ff62ba1f80ea0e1a4754b7182116f636d137b6de62378cbcece344f4442a2de3bb008229a9759c4ae789495a168df810d45ce598eae31d4012070779e9ad4707d3e283eb7678aa2b0e6d65500dfe197bb38bce87dd6e31c02d20e8952dfc92be73eb296b9dc6e5187ee617c337702660719f1fb7ecd693fba16271e854f8c216963baed9f395bea82ba2d47bf7b2c1cef3a8723a17569f79069f2dd9dcad80c436acdbcb8151238bdd5e1fa5274fb8c544ac14235c3d92447bc3937b81ca2943bef413eb152c08271646f3aeb82063e8edc3b3adf486af3edb670b0dfe8586d78a31c5d096a622adb483d6fb08f4d9233e8eab296d1048873ab60ba29368c82a9c461263132e260c1d99c6dcd95093dfe41f04bdd6966351dd7cb90c1d603bbc31a222d1de79bdd9012d2c64cb2d4625a565b4e42bc2f998890fda1cb60de84a774bf1b0644900e995fb96502c72f45d1a03cd732bbb3bb5722db6473168c30e4890d6c33e2a6895f5c07f9a1ead521b321f120518015c9da8962afff4b15cd4181e9af542997ac2609cdf1792b76ee2454b1abeb4941b409726a64535f828a450b221794aff72c5c18f593d5edad918c23c8717fcdd71704eee6f7e4cb47c7b5960af504258b0251ccec28a28895c4f974578cf713e288fcc3b9a8b004f38d5eabb16e11525218b784fe7ba96697896e1ef05a37b932b7105bda4016f18b43ff9bfa53893d37121ad7e4636a573cd1b8298e18dc8c50ed464f1358b9131fc0ab44edd2bcbece56435bbdb05f364375f03dc4109219ce78070babfc15e78a01f2d362235946868969f64dc5d0dd98a3c4694761ab86dbe0bf268fc23e520c8aa4a089f283387eabca356549b78d0ec89b0265bd8571adc27ca910e13d0bd2fcb465dc863e3ee892de4429e35bd6cebfcc25a8c0bf965b0af9bfa88d1d14b208990cc563ecdc1ad977dde2101ccaacb5767718171fe671a5692b848e0d0b5f459347301e4807d467606387f63b301715d18d959bc0132b0c580603daf1099e06f93a4bdeade87e5261e3345487b8d08765dcc7740ebd3880c377fc512552c930b5bb26015c7b3e59aa357385cde37120171ee7f7d427600139951114933222f29254d59ab03252e6e441a0133afe8c2416e705dcc59280f5470e22d72c695f803f52d789327867df1172e90509ad4a49095b3764a6f9f22051c32691cb0c4f9949df8f934bb67b63dac128f1cc1a098c376f1546aef74f30f66a7f1cc315acab3c2dcc74567f9362079f1ba2a4f3873a32b79e38abdc598a8cb2c3b83b01c73040ae125201205ccd972ce49421c6d97d73f13b0b2663c9c2b49660a5babbc2c786f36feaf8353b6d4a772ca1d2edc16a390be220f979b6503e3eb97fdbf58f231cb9003937ee18d67def3c8516b6b236a9b2f3f73756cb3c9eca592c064faea9369f27a5f6f37ca7393dc426caf85978cce3f4e240e160c77919573297a43230c206993f1632d4902490b16232ed840deef7a7b244fa6f8c1d20be77430cad060876a40d0b512c7d6c4f872d0398699504af026c5630d9da5e4b465c51aa42d27127e9a372259822948a1f8ba000ef0f414d83ecf287d6b6080bb4023cc53c83af0873239f25c16d66c61cc0d57a40f03897a00cf664434a3af9f9b686afb162907704438b591046bc8b58fdd0baea396cb7a67641cb41381f9687a3d2b26352e2df11fd7f156271fe34ecd6e28375064d8dd22ff8a49c9baf976df35e1be92eb7e8da67809b0f08713dfbfe0fc005189c00361a595659b768e40dc0729d29a5600be2dac5d1c5696c612d684e900c2228dc8f118ffec61fb389e0e7384cad7e6fb18ae059185bd66f9b550f931a03e136541c0e6fd00c26c7d355bf88b3e002ff5f37bbad73eb6fa078850f1c0bf3838651eca9e29818ce84d071df2f2e373fdc5249cc6a61b0b2203cb2503018bf584e8e95c3dd907f585de072dfe00bc1e62d053c68d60fb2ad97a42fc723105ce3a2c59a9e52b183edc2e18fe83a6ec1fb282f97b9ab3179ea730d3d618015b7930fc18638c2f0a3c4ec329486414d161bf055090eb28360f1ffcac4a20d20f11c74b35fd782e1a08bb07223a3d7a474360f5e7fb629873c2e08f95014a4f666937cf23d7a6dd24599b3b05c05f6b771ca16a4ac1d0e4726a0f4703e2a6dd7eaff350d078bdba3f616adec3dc111df654fcc9716873b3fab8990ff646617572eb93dfc67c742814138335bbc8c71e8e26b34dec2dc649a7a83ade8fe3fdca30d387b67964cd81d99f24842d660f6eebf6ac26716b3bb757f0d18e3245fa79d2bac8a5d17d6ba17d032ef9a3e605f5c0acb488187e1ca3726de24f657d90244a809fea7482b8e8c4398cb522c198140f6f0616fe335bfd19fac099f39fed9177b3360bfdef33891e374c078c246e0f899af60de0a9d9ab07225385f056b09a982953542fe9bd05d979c7e4ebc5d8da859b2eaf71262ac0e4f9239afd81ced68be0e52b35284fadb13f90d353b9383969dd34a211a8b1f22cc793084373206821c4073287fcebc3facac8cfec999641f8f810469105a310a73ce90b2535a4a0f7a5793f14d54004ed3de266dbb58c3c29cd7eb0447d7d01a742275332217227f40ef14636661851272e92c240247f67b61749a299722340e103442bdc42be055c08ce98b796afbc5a82e1150bfeba379d75c44ac7d9e176951c82fae8a482b5f3836c2047d9008ecab0695301e4ce20ccd15921f4e452b15eb53e5e25455c0081369ef0c15562b225424caa7d18c2c0bfcb28ca245fd5c86ff061965579ac1a01a888df2a425d7359bbab1e52b52905b17b1c699bef900a3306bd06b368ca15e7c5b9d0abf5c1e4ccf6a0b178366a5de706a723b9ac43f94a6bdb9ad40a30f5baf23f32b9ca64d827a88c3097d132cf3c7e7046f3567958e9ae462c5d0c2d3767f4528f8cb610acb28b2195315d887356297b0d13a982f5d6613426f2de094ce698f7b6c9ba0321cd813e8be095e800fe9afded06e162bd4177dad0466baaa9c18332a70b81ac2e69f61557a5c631e4e9c38fd2c7a82455116b21664170a9cea8806c6e8d5638a62e6c287fe1dcd97c113b58524f812bb65b34414b1ceeb1a33694cd781a6fa67855e60d53e230f74907e6db9978382123fc69aee6cb6630b527e53c1573a4b128dc3b718962acb148378bae6ba1185c952024203ee57cbcd3f962b61fe4573d05a33fc870ffd31e63a1e7e8e04ec2bda3634bc21de85460fb32474e6cd8e90c4e25c5beaa3eb4fd0d09f71cd13fd1b402cd92d1a6018f02fbf04542302e17607a36904849109773753aed2409e144ee991c09a15518446b0ee78dc4b0703a9077bf8f77e959baf07be9c58e4cf4b3518b27bdcc54facdfe787c3fb33ac5ce3b600a168c861708377180f8ffdecd4e025ca730c3eaa903869ff8a0e061aaadba996a481a6170ee6d86129d118959ebcc8971a3548d9c07a4c5163383993e173be7aa1d4b44f284e2ba73958df8fd22fad0be5e81f1080ba9f55785e789472484ddb05943dd95792acb6b6c55981fd5f87e3a4dfbfb1a87cd009d6d66d416b36988a9f9467f899366caf3c08dda1bf20ee79dd6456a22eb454a3970708e058203725ff901e5213e0b10dd2c75bc2fed86ffb33bb2f10a33de85bcefa819f12ec14c1ee8668c36d88052d150ae40b9c0e362dee63f26cde7dcb340e5e5f98637de3e0f9422300e1fca2b77cc38c073c0abb003e124b5ac4af0160d582658de0ecdb4375461923a279d59893c1b53fb08e725aa0cce559e441e9f6dcef0804d44ba81ca288691d11b5a1b9f39e66054e2b0da9032e7022376f4e8c5eb9aa3e849d4627cbb714af782181d0e281300fd2c80da4d57a9e7591e1046620971187b527f61b2a1ea54d4ee9c934b41803ff9f84b4b23fb8167e6afdc1b279bfd0266c0cef78acb5fee1ce13f62c5b600ee7c509e761eb635f25dfc0de14a3008596675a14fb7a2bd26bf8fa3c50a8252ef691077ccd4d10adc48f957a2ce70ceaab18828cf6e11b746b136f62bf7f1cc06f5d0f682035c4dec12fe20f5ee0820709dda6a4c6f1f0af3e333638b172be569b6fc2115891176c0c851bb10008bf0aeff12c20e3c2221c8a1a9efb9cd9a7fbad5bbdb718000222f47e5d913292fa83917d7401d1aa8689281711605b1e3b7cb38fb91c676a171a4ad42c02dc084a9a20ff81cc566e58e6f71b5a431db4a5c78860fba439b1d25ecca735db17e85a239e22be1405ad6120bb916bf9d7236e1a56235854fe1eb089f1675fc02f0963a4e5ec40997f5006d58178796d8fd2b4b833ea15485be3627be6c30bd0fa61e22c5abe2e2004da61c0b613ed135031d39924afbfafededf92b3516f28f86532c8e136e98a294279d344b27d0a9283a444e3e66d8e04d1714a49f593a439522cdbd4b430988dc05580110aec7b5fac42760e8dd2ff6292899c7a41de08fd8365666b2e33abfcc2a0004ca2bc10f861690a0563c60dc0c4159af5e1932b2672a84f7d3ee3feeca9f3ca8296ed04b515fb4e2c82b49989c4df38aff092422b96ef4237ccb2bf0957c3b5e93ff4b5f2847296594b23fbb87fd8f681472d3c346f313d03814963fcc237d7aea37eddfcbbe9efa0bf41465949e304c4a27fc26a693ea7ef2565e4a3d7a62209a514917dfa5d9c9ca08ce84f4af7264bd2b076e73e70afc13353c6245a5cdfb912e9d2884d8496c7f6787b2125f631b4b90090419ebdf746988c9612c598749b9d9ed52fe01acfe92051fe60048080b82d6748d0c6608c424563cd14a98d1c809559a476c5145a32aecd9937234716b42be075a706002db8dd75fea29a552e893689ebde4c01a2a12c2e9bedcfc8b6694d9c10dbcef35b9eda1d9d97d75f4a71386f08154f0e6dac171bab1f5e30e63cffb2f0424fd604d9cf676c980a54d881b8cf2a9068870e83c880ee5a00675266a7d547bdb1e8a11417306d08907c25e02a724dcca5850832a773202e5ac041e732d6ab01eafb4b7329782f5b5f68cf5607a6dfe32e64cf4f7bf119041174ee0c6fdc44a0cfd1430dbe97da11f439a6f584d15e34722faa7fa6e87dddbd08a279c32babc6c292e96c744df05d6300571727e9d664d2a9bb4502bbe6481074dd79259a018374a8225b3ade4b1c35d3f15d59167564fdb2506202088f6b1962cd12998bb82e6461396b8369520862000720b62b75835251a6241818efae61bed0c2c987df772022288816efe862628e5956213d816043dff88f4712da67437548599d8ec3ceb3dc1ba26d03cd31d71b9562f59c9a7e7d8d209103384d658206d983f1661ed213814ef5a9502d1a7b4f696fb764962b859d89e82a216c96b94088a73963c97a0102add0c5ce5f9ab3ab5c03ccb19c01ba28c31158bfbf4f7f90fc8f76c8da3795f03cd4a2ddb49e6738e6e65f4eeaf19bd3731e34c7a68c24e5a48c47daa135bc250b496cc8795ad494e614b15c06d1bb48905e70fdaacdf066e61c95b972188481aa943e45af90a89d34e0c8c4d2ab039159440a40924f087ec159061b663d2eeaa800d69c93b72f2a5cd4cb23a5ce403c24f8963e7cc2378240ac7cbc8183d56f52d63de183b80e5daaf7055edb14fb6e9ebebf29cc3c39ab3fa33188a254f4beb876893644cb9e11b28c49cd6159c91618f9dfd1b432f6d2d057bca22f731c04499b8c86ecf8936e09c4162144b82f2df57270ecdd577cb0a735317f6dd0413b68173f4bb7db773d1d9ecbb42c1e0d04a20a2fa8ee98e554709c1c5ad78f4b5795ad7d360b36b70897ee5ab4681d275393ef9c8cbc8b5d1f157280885ff00183fb286bb78df7b9ef551e25aaa50e423f94f507718792995e55100c0241580d62131ed0cc019a083cb40ff8508def866b560335a41891ba1a647cc500d1937afd17d51f357c421ccd6f23b21f94cb51572421dd6e959a77842d81d62ec3e401f66d36eb01ee2c2c16b95e2c96b1edfe93584a4ea03deb49b6a6e08a940f3b0b603e93a74106f25e8b7dc2f0690f1c7903f204e8098bd629d19893820769800280b8e58b8a21c9da7bd941bae8a173cd3226f511fc58b4f6b6a1014b262e10a90135df80ff981c4ce678b50ee6039be1b81b4b1c35b6dcf64b7a7f8649a0208a4e9c0d41673746469d5b5db3939e84d1601f554a02d487687dd364312c8f98a153e0b25c849d02631a4ef245adbc26f7ee225a824d9ba93d0330a32c9eec9c73417cc5a07e27e8bda06d6e4c830505fe6e67acf4aeb7bd5e3efb8f31d51a5c39c5671cb581204df0ac3570cdbfb5a119754bdfc62a8225f2c7798db5dd31d5d6a8e3032a34da3d1e5716ae9d059c35faa381852db7a683c068409c8edf9b984668fac2f079ff117a5b853bcf1c9b36ce52599ed84c8a3e8b888ef23b11584a83d707bb173703008a72f260ebb1bae69f8540873fb92eaeef55faf0b1cbb9209ed0a8d4f0e7d075bf7d4f57a886210e401182028ccf8bc3599800fdb56a94c47f40d75ef2216bc1848e83684a4735d70f980cdd7d496babec59fe381f53c24a942f36169a20212bdf883ec9b8a7b5beb95c6b20117f900c67d2d8437f62ca9482a68a9a1ea8ec880bfb3aed37afb573ad3c9b259354574f1a4fd654745e21e28c0c9fc26f9d9f0a38b8dbfd079f517a4815351efd96537be1a68cf0add5d638596ebdae729ba323a4b34aa59d11b0794a35980f5cc158838304e4a45603fb415bb0f2e9b0b7abb526d1e694c7ab10398d332e3e2c490f87ff9b4cf33365872f43f10d9e903f327b7065745562b1105e8931ecf2131923f1b857dc1b92a0c733d696430269cbe5c487a4219426e9516edc05abc96573144b51b6073a9e3345bf9e5b4acdd54b3e3d73804a2795234671fb8de12bb25cd40079ecc2a9d0b2505dc5536a0736e8d17392816eb090705d8c24cd47e0c154029533325e5e271b4959ea4b48b93a9e6133e0e85a36ab73046f8e2c7cdd8ab3018a9b47ababb269237e2633927f6403c422db9e9a67990294d24cb1ed8c9ec711e3f15cfa8caf14c8be59302c6b1497f4656ad76c04b431279927b9ca2c960b80cb038252cd70c115cc5564320312715c50b03da6dfb8a0cc49d3a5f88f4e95c652f3b399feaa56470b637598d9370c4bca8abed37712dc2e8ad4a215dffb3cceb47586d94bf6c14cf3358005d5fb0e0ad875e06809ae7aa02a8c385762200a640e823064e55ad39bdcb13c88a3a249c274013b2b62fa963da656bb2455316d8d8ed1e89068689a230bc6b9a507bc4e40b54e7a13dad93042cc31fcea3a2e584e1c9ccdd26cb4c18ba8fc7c4e1a3d07aaedce9956f0c740b21805ccaca5cf6dcaf57de3a0bb3a7f049d34b623b9c5210f69f0b2df56a559d4546898d01f1f70db35bfbb5eb6d3b20f32a3d48672f259fb92a9c00b699989190307021992f2c816edf2fd0053f30f01ae439caa9c90bba64b05772635ef2c172dd75c4ed823b3b53b27d63cf35895cd41b1b6d6dce54c566a78c52ab3400839548144b0496bcb6bf943f89d4a87fd47a0b60b08c90a10af9e1f880d43fda5d4c31515ba4792291171ff6a9f88706e0cc4be2402c50e0c2ac67ffff1445cf49990e392705175bd7e0a6fb62712b0ee49abb8b1b644c17e58950c360aa982b04cd3ac20c8acf68bd8d25d13d76785a28568d5258310a4f2115510bdae15ee32c55f13af6b17c76db8e47a579ba6f1ed274b430d94b663db941fd0aac1130be8452a0b3556ab4c0feb30ecfc2cc565fbb67e4d512670b73929ec4f576aa68b8a513e4ed71fd0d0a1c26fb46dc9199769d4955878ff217198f0af234878b249717a46260662e0998380a5b4696e9844ba879973d9f821b63330e1f97ef03074c127bd2d01a65fc3e3b2538c57ac2f80eba6e8661307076189d6ba6f885b734f71b37c8186ba96e02e71e9a3a122670eea685e77396b1df32390299d4b755a8470e3c32f83db440061e4650c12c3487f059cf7c47fcbbaeff5e34f5095d850057f6c6071c69b4f2307b9419d8dea05fa7005d8a14ee2bbf5072994fae433a0040eb23d9c182e4a9ae6af16d8b0aa297a6eea6d771e99954dea94560df7b8c7b68ff2a244b5b1f209830cd954957defae69c46bb6b3b5a0033c347d2a2223601630afabc1fbf95b41f3e6a8c84adf38ba0e0b33795de35229e241ec102909e7fb4562b31b247c166ad80deb2ee2cbcc3c766f9bd9685a5ee6c9e87a29d1715bb1359d2371656f658069b7b2d58502260033cb1cddef31ba979a75f794d1d03cfd0a44f8fb5f10e8267dfa297a12968d1d881656e2c8b6f1a9df2fefd06ada7a4b571f57c55c472266cc4c02d1d3a9e3ef95efd661563efb98338dfe4aba7b3bc6b586ecf9095797866d43a94dfb8f9638ad230b29e751968ed7319003ae232308fe1a4c94a74b24127cab70ee015b8195f45ca77aa0756649184125baf94d1a31cadf7049c2cb661403f42cd1df7344846571ce87435143e69a41f659a54bc8bbb44d0b12fac3c54695ad357ba3c0cc0ca878b09b8c27864add75e145120be84aa49f477da3dcc125fa9180cfa40816ecb69fba7c9a1c9769a3c885234c0dd48032f1dffdb25493fd91f979170a7df93d0d7cad3ceac1bf363e75a1d935fe1040ce51dcea587ed17c2c3dd9ab0a0e2125b7ae948f4da4b70093f0250b1320c54b63eabc9ad5bac0da9d99cd07fc6661a0780c8b7b71e8b24387741b1983bc1134c8c69ad60623089a9f29300a12158799aa2bec571fef9b14d4d472e16feed8d2f08286af04718afc0309bf00c4d560a3024e352944fd5caa484f9dc3c7c75521cce5426c0567d0c378f0510b30421529b3bd55541aa371a3b14af59825c0099edac915ec8a360d048bca05a16cf1d41e28c0ccc31340325243a0dd0633fe1744a28859323150e29a200127b0e346c01a230cff88d1f0827601b7afdf5391e19c8b60637c839601d0f30cd6b0655b38f8249fb493de113b1ad1f0802474c6440e2ac3d1a2e0aa8356e9a5b6970d7c7d3f56519d57023d33b3082a5f786dfb01dddcd0c3a97d0e3eb1298c3c4a03ba4e5a0bdb5c5689f9002692f34ba23a6dbf46c263400ccf38616af6b73ba5ca63562a68e92fed5783aa142fab02a96941bdaa1ac91e1527a79c6b2b9f87d47cd58c0d2023a8dc6fada70c39b6e23ca4fc41056ed84335996e2c46cdab5c7fa7f61ee1f5ea9d67275ca2e9041a7ada11ecc6870f54132e562e10c1d1547f96e2fd75a6f940db3cb1627c2b13381c6f603897b3b3fc6dc098cdaf9bc561a63b8eb0f71df4d12fee0bc8167359e780f4301e6134fbf8013ca9c825510bc65b57be27b440de09fd00dec2ee0d4f6048dd4e96df5de997aec7b7730cdb41f7a3a48c2421c0b2c41293dd2f89d77b27cd73f4c451d13bc67ea8e6d405cdce68f683c54fe6bd71e014515892510134c94465c1c1783d7ec8e243976d9e711e852c37c486cabdebd9c24826aa1103ee5b48dd4823f416158b04dea13b687bc4fe41ee9a783529c692c420bb7a8f6fb438b653007fe0f33c9995ae59dcf3eb9a274eece7e457bea49b628f3c43517c05c2027047260593d24926884ef2aa2de480a50ce5ab383ea3a8dfa8e1f70bc60d92e54bde8f37e8b089462d7a55d33aa8838d76d7a925ccefec1e09ed74fa9c6768da299a1c9382e368b3e99a22a72d606c084a1779eff2fa451027ea61727c391c3a78c7f23b697ba410325d02eadad416c4ab3eb4bce0c5819aad3df174bbe4cd313b64f266b20c2385440d1a0f853aa25bac320a6972d5d22864e39d0e108fec0593d60375bfe10747e99800c5af641a3c726d00e7c46b099b789885c82dd4beefff0fc9f4ad1f81026cfed37e864d721b7bc81f054f448263f06ec24f3d800dfa4d7c982356ab7504dea1fe1d539bf8426a5b553ee98fd59277640e87a607fe1483b005bb0ae68c9165f316fb8aae0f17f9a0036f39086f78894cb151f10171799f1cf5813b96327722e9827d3e8d2b928761ef93207a181507b9c17333fd35d37e737c5e99efcccc3c10d4497e081dde48f1d46530883b5f791ec12e3690b75eb3f622bf1cef93e0f7bed99561601a82e142b0e41afc58532c094eaaa8b494cb39cf61ee212439e829a64b06bf626ce274b45dcdc03dbb086c117f1a6ae4fb0110577182d685bc214891c106cc00ac5e8388e1bf3a5afd7515424556da1f863200f8e6be8bb8e992346d756fc14e15c19375dc759bfe8a8eb6228710e7453e1df33d2968c02574fd175fa5f2939b73a6a96e157f3fe8bbf853afa7ffc5899f33ea9f1b93479a5bc25b0dd4cf3aee321702273b3dd61d7a39202039b4ebc1fe4d0560866cc9b70a4a3c2f252456326f0a5baf350b5c0250c5ec8d5bc80411f4796c61a278dbfc747a29b29911e6dc6268d3517e9d0a6b92f2b21cc4c7d1b477f253a936c637437219ad023164662e26b4ef84ab0c2bc8d2f8283fa746556808d158b0736719098f7daa763be080ad411f29369da5a860afa9dea58ba1444e4a619f0702db57f091cdaafec18f3b6b5f7299b051027c467cf8524d8bf5c884d2957b45d1dd1368afc8f37b814c8835c8e620e72a9720f94c529a6dabf940dab0bb13f58a11ea739970f82309cc5bd7194e4ad71aa4678b8d59099ff0098e0f07bdc7e849c31553a433f08b40a74f3428a4780897d098101927865a0dd06245e49b0fc5ab966355694a4456ccd10ee094ebafecf4d3926c3bb91339bba1c560328f4ab72f0f044c964f0cef4de3f20294f6d4231700098feca547c70134a08e619933053e7da790e57b1372e97e30f93fd0692759693d6eaf26dd50949a2f46dd6e2781454643897614482c626cf7488a978648d792760eebfbae1d695525420278f2a0788e3260ee812ff32e49510308c06f984a9cf2f119646eddc07e601d561b4718135c457e11a84ef3b0745af88db8f7f678f0f8538ec21c940c9bbd0089578d4e1a7a2c4e9482187b492873dbb2207e6aa60cdb679205e41c4704eeea4180122742587e8c42da9169476d1b95ac1ec618cfe0b1f97d52594b3524eceb0511edb4fe8bea1c93945459998eecdffc6dfba4967b0118e37a778d84c20cf9d80c24e0579ce3ab94c51b58effa322cb81f612314c6f2dfbd4e903040cc55fb034a83c2533dec39b6ce264adcf06542120230d530732d7ca19bfb4e4f6b0a8ce0783534abe743b83d585851e33d1df82c6f87bfe8a0b26c10e24c13ba7822c3d7116c337cc28713e44b6c6e73bc09d2b71629a6e10b53aedeea644e14b9c5abcf5ea9ad0ba058e7176a306f9bb152df4e752f36aa4187c1271508b9818138ec0c593481434f402a10831bf731b6f2a4cae5e117cd2b85e97fcb018c23c9fa5b2366f457e5b07bef03b334a54dd7d22ff8c4b0fe5d2bdd5b99549424ebe81f33b40691e12aede8012060d99794f5e3bec7c479092dc1fd1d59cc085b4cbb9cdb3c36a4427846718855ed0c18f845c7fea6eb57d342f4e6cc6d2d23cf799c2fc9e849177ce8d009070e80c02ff9da22c995d1f17780bfaf4d12cd6d559b6ba5437dbc93f524805548ea5753b16c05287c464e8dc49d50046266d12afb04b27c4cc070b9f425b7c30a27759ea633a46664f2843ca657c61e436b1fd31a81ba815b75feac002d125dd925774c919b313c372efc5e5b4d9ed5a9856c1837dac1c419d29ba0454980197ea02258e0e58da293a321ff5d04d494e979de2782f373f6c5aaf2376ae7f989740894ca301f2e39fe750e3005c27dee1f8aaa8a7d6c6921807f9141b4849ca0f207663217768568524cc53c8fed87b00a0174bb5193d73f2560944f0e67bef8227c40c71c0097aa7ea4ab9dc46ac62a7fc09e117c884a360d9dac2b9723844c9081a8a616dfb15f9cfdf3996b18a727a5bedfda4ed02303e53a84cbdbd0d8a5c4b2ad7eea39ed1cc18da2d3f769b8928e858e5007231b6ff2c922b70afa50fb37eb5e692643105f470ff0686f2aa7d343602527a81358862aa8489cdf6f8b4fc5c2bd1d143410a273699e21f6f62705c5758403f5a29d945dcd8f1ea8a3cc4ec22be0e105bd6fa67d26d823b0df7a3a20ee3c13c9a4762ab644c1640c9551d6d6844119b2d868ae90535235fe85675a5947f0b156b90208f2cdd907867ce65102b722e1c400ef0e77614c00314eaeabe6c350b16158e2a593d4d9584f98e243076051d2e43c1ff569b8e8045ec7bf5791cb5bb348c9d36ebef82b85341a2179d0ba3f29c635912d053a1443dec2a0a5a203e0f04dc2a4f6195112686314930aec2fa397a11f1f2d6d2d5870dd25edcedaffe7549bbb6d902138606c24899d79e231f72edda06fc0069af96f8c374c7be33f13314d47ca89a8e1199d74b88b93bd6d26711c3dcfc658f6293b9cec3f6f18a53753e176b35b349d56ee12cd8cc7503772962a4ecb422def050e803a9c04fec2e7089989d168a4ce165ee08bc85977f09794713756c99c78f148cbd1990699faefca5f0afa2ae4d2625c34494e55cec97b830e7d5b619a935b35f7ad68dc07013c68c8779bc8c4e74bb05f011391ab38b348290a16b58de7675b41f50e7ae3629aea480d323028348c35d184a5349020335105f3449e453e3895f92316d5d231506125ae66412f49e5eb070117d052ae284d770d947a8cd24462093a2792040d95822c8293d7715a281f746026b2a5727faf1f2f7848129098e41016e62adeba0b233bf391f3e20c91e545df7c996f7ad2406788582ccb5381f119ca18bcc74dd5390e7e00af763216af81913ccfcebced47dc6bbfc2c16a8e912f0d079cf783ca335532aa9d32b740fa083a1efd70ef776dd3e519c48c033b4a83796141d806dcf6c7863d7abfe7be01535c8ae8ee6a894faa162536a99922ba7803ec5ae1edb7f93759cb71c4204fa85e98dfde1b0f6991be53ccbf4ea5e4e6ab38cc981599dad3b6e685b9aee28f4ddc8c8130f0f808416fa22d0354570c3c012ba96a3a1b242e3938c667f9c8f03a61aa6ffd4c45c500293997c14d162a5c41e05276462aef2e7ce4fbf962750ff5c1b02c0ae2a4d9a9c2a1c6102d1c79d8ef88096dc29d93af66634eb471aae05052947f373d46faa5bbdc72d5a86a2dbd4f367997b265ab1a5730c24c0e6a7e65754f624301c15f573968107795d1d20b90059abd4f675c10000f7bc5052f104c227215c04ea9d01dfa046155be8e0880435843b38c51574db3550e005d6cd30e0ab86ccf78a9cacd202665eac3946dcb6b59483ca4d033557a5b55fa3165968ad14f208978feb4d69fd53b3075bb0d9f082eda1b0b476d1fde2b35a14a495c1a69e2a6cc7e67506bc5b0ef1f4b3a2368b2a8643a6b9e3c782a1a1fdb9cc8a2689deccfa3dd3a493187b54f6bc5b029e55d99ff2118ce80b72a328085895b136e9b8e3f62cd84435ac3d79be475cf3383ab9649280f90e7b5c5c5431c795f94f5b1236c9c145aadc0652fb788e3a7f331b75e969988bd12ff12a0027859698865cabf0d9d6ece4cdfaf5ac1193da8a8e77da4679542100f31e3ca2284c5aaf873c4e83d7211211267cd3de4d6c46cd9f5e2b52bcb28bf87aa08e5a9dc82845b2074231a6b6c7c044e32212be1f68b05f240a673e154f5412e27bbc73ff12508532dee3336a0d62ad7fd996d0e2a8f8933126cc7702a643fea59090a79fbe994aeea5bf40aa1635fc593a2154662e2ffa60f59602e35acd4628324d123ea8106afb1f127e495d710931f7e537c910111759da9e11d3575242a06abb629d047105b3f1ee41f83bf2414281ea8c097b2e9ccfb8de57f1d09560cc499b50de20129aa1476597bdb3dbc9988acba8bfabd53238f75853a24b0ae275fc54603619350068f04e304740df164ba34e46db94288cbfe5e05d7196fb1e8d22d968a090662dec38d32aa711b7b2c33f5b86cf9b1c3985b72b810dda67c3b2a17e24a9542bd32be179096849ccd353aafc276cbd4a81fce2dea4b795e73457bb88217cf7ba621cc1045c6d98639277cead96eb2b9a67a11c9e499d7ad83fc36b5bb6905d9ea7ed611f46e80614bc02668eec66da14e4f9a154e03bf43639b51dd5790ffb4957e34b837bd8b7446140d6d70c2cdc050e23d41aae83434ec361e240f8aa78783fff790e5c41e6a7fd3045eec4a3087cd3b8dd266d0f6da9c991b836c2bd6506275ca336492bc09393dc77c5e14259a59fb73bf51a6f6782e7a93d9609e3a62877e6a1988d4f0a03304c05041a7193ab58309855693a7f79f6efd1ddeb6534a8f1dabbea165f5291e3ba7a8942646ebeab69cd1c6eb3b2686365f0fbb57a32636e8e832fd050b8e181a6dd6cb2f6cedbc5607e6f62741b03b258cb5f5b6bb6971a2d355ad2f6de72ef2d0a620ad90af851ca086d16bd7bccf8d7b38ee67ebbdf445f68933b9b8c455df7d5907e7d3f48bfdf8f949adb7d353793eee904c5c47472626262a299689a764ea471269ad689b4eb2a695fa954e2388ee3382b7497b9eb6c321e6d5e6cf062edf8d5455dd775dd8cccdd3de78537739fda29c8e8f834a48bbe286570c72779a267d8a3211d7fe4cd1cd3cce0df7b8dc8dc6566e775e66866dede9d8274e74e63a50ceda24f3b771ad18ce8a31a0d37c3e1ef36a85da66383da6baecd85c13fb88eaf2bacc956d63318f661d50b83c8d8175a8fd3ba6ebd161bbc66b2dbb026d75b3c95a042923b866b2fd3b19bf214ed2837d9b89332ce131d7b1d8de8ddd7bd351ea4731508491963181f3fc3bcee23cce3b8474d47e9d88761177921f7781eb48f380fc38e4f37d2312facc9d82bed07fc0e63cf38af7b26b51ca3731ee9ab4026bdfb31fa88a0890dea287df498bbd2171bacc9dd47a490c3b493bc9084518d8776ecfba17df4fdd88e9df36a48197be785a4af8694b50f6f8f3f99a47c473131e9ba7b7f7299beddc548c71cd69d747cb1ee240ebfc32613e699f03dc6183fa35a0e9c1d3b15469779f4eefb717f7deb2edf7d2196ef8f3baa1acdfdc5792129739924c217e18c317e777282bfd0e4a48b44dab9789257dd6474ed25af0899471fbda74633faf5b5c6e31d65da9277f46ce471a32f947944bac82bbdf3b2a9d1885e7ad466f04997e9934623af7336b799ee98d4e0f6d668f0b73d7e3feee5372fd4be1a52bedff588730d8933014ba6cf2c5278b3653dbebe0a91ad57cf2ccbaa18b6f5d7a3dd1285882c42d4b01c610a28bf34d3537e4fdbf14be7bceeba4cde95debdd278e06f3c4617852c99bb381c3e73dfa9f4d18cb677e7ae9b78e1f678d3459b77992ed3f8e2b010dc3b6bb76d3b8d8ebff0c443f4ed310b711a7d34a2ee9b10193f662172b7998e4b9fe9d7f7c374d1f783f4ab86255faf61c9a62fac6139dd4bfb9e72ef49f693eca47bd665272727ddf67af2abcb4e4e7e6d27ddb90ee5e4434141e9baaeebba29e0cb8ccf7138c3a32f1492471779214b167df34221f2f612f785cf5c77c235d885a47c72137715d317e2cc3568f2953c959bbcfb42d255bcd82d9ce83253e4c56f5e759217d6b064fcfb11c61863a9f1c0f8db394ff4ce0b5972f7a9d170177db1c1ae7387b98fa5c11a96f823440d4b2e42a2f4c6861ad290f2b0a837adaf7ed1a643235a26818bebe49808e528f7a68f484fc1c78eb157fae671187612f785af341ed831fee6c5463dbbf665df96bde485dae3ef3bec65217e864bc78ebdf0888cb9eed8179e7868cfb0735f78ea3e1a8d8bf988cc5d7b95dd635e88bdf4e5300463dfb3efc77df7fd20dda643fcd974de488f4f19d1903ed1eb68c47122918813d98be16cce8c4516e38e33ec1c767232359ace27c7bc136c3bc7611c87bd9ebbc5380ce362fbeee5b55d7edbaeabaf26a49bbbae19eda46f1ef64223327e6c54e926dd4b369dc30a4cd147dfdee17b2db43949944523d15784ccf5ba3c4d6a3c34ed9bd71d7b53a3d9de7db1417c99893dab358e66fb688357cbbeeb11db23d4906da4496a56f8dce401e4e8430550166315431fb956d565f85c738cd863fcb26abfa9f120248732398cb7a93ce44321b93f65d0c51638cb2454f44c31862c801c7da6d8a9599685369f7e3af93dca473fae73bfbe6d1e9e620a9d7c3d650a1c237c91714e11684ac75f48c47311295974fc4ae3c1fdbabe79e1f3b66d1be9e3414816e15f57d83d9e8793937e2d40e693cbf4f5edf407d1b95f5ee8235fafb41fb6e3eea1edbe130f4272774c3a3e57012199f4596025779f8f8c1ffac8dcb987ddab1d329f9cf342eea22f872198fb789cfcfa7e9c9cf4fd48f96553f375ec85dc6753f3c9171b3cd59cf2f89117d28cf2a9f52069523edae0e9fbfd628328f807fc85329b6ef2a8fd60ba89c90b37904df0b7679b37b389c9a5f643c87d7b6c30f491bb39b3d47e08f1b96fdfcca62fd44036797dd57ee86cf2eb077c0e48eebeb0de66669387b4c1ed7833f9b2579abebcecdbb72fccaab7dd666ec73c1b99af4b2dc76643f3f519ec6e814f3dc8cc5d66eef8d3de1a4d768b2f8fbf10cbd98fec16e3214f054f76c9d1878a213993da0f99f532ce93b7465092ed33d147f2c267d23b2ff491bbd24ade8e5fc25fb81223060c182a2aa7534a0a0a8ac974726262522a9148a39148d4751873dcb669dabd598661d77572f285366f2f95bed0666ce5ed27fc18634e6e3a7d6195b7a33cc58bf105e50bab8c6f824d5f4833bec94fbc185f4cbe90e6ed256f7b8ca8d2b7d13b107fe13de93293e4c5775ef59117dad4ccfd5ee415213377aad174e62eb51e2477fb85d9b97bdbb3a8cd488d072151a6b92cded67470dbb1d77de2775ff88c2f3339af33f6c27beb7d6e47cc3974003f6b0f9f351a19decfa6e6ecb38f9799328d79385f5ecce17dd41b22ac36b4a10d43d986d294b244912429b2d842e28178fc8eb8c1fc8090afdf114708a922038239e1e8073153a7291f3f0dc4cb78f99da69c1107bd2cc0b753c0218cb422c39e6d7588a625374e920683be807b26b98490fcdc799d90736f496f14cdf72ee18c7c6fbea67c59f205f37d6cd4bd9f5717b9bf93035edf7921799a6087caceaefbd375a5621220ecb248a3a0a019bbf4e9f1c958fc0249c68072288d64aceae9b902d6832463d88132f6abafc7c3136a40859d838c4d0edc7b3f43f3bd37423eb1dd7a277b21d9621d7cef8bb4914e6dbfd769f026d12056023f81731cdbfb9d44becfecb59ef5ccebcb9b6eb9af5a8efe8eb8e794e0fb3ebd9752449969a796434a91effb0431e6fa7da340dab0bfa7debddd259032ee97c3101cc990b15f5f9320be60e7c6801bc37e44a3eab1cb9b46d9639749481b316343701a959dc8f5b26bda9d57bb0d4a9ce6d17630b10ea6f6e25a65a53919d6c11d5a938aec1c86b4c4029b2072571c8122478d867322cb32ecf15a9665599665693d08817df3d78b11c4eec146dd2f3c22dbec636970f668706a1ad6d61637982ce09c2718032d43d501b6584016900564015940225555811508e30256555565d9b7a37a78895955252358e7bb2766fdd555619875495aa725e42cb1eaaa9295ecaaaa2a53ae22487f79d56aa55ce31440f40cb3abeff1e59f4225d3872a21e966ca92a2b1855251a351b1aaaaaaaaaaaac68d4e694fedd03c986e71e9a23a808d1b3b5c0850c055550788d181d53800f68533997eee98f33576647a93695e43987096d56854b46daf3001d698759b491c9977619dc561ad1c955e97090bb187758448d1d01517492c6fe6a6dd565b94524abdce72c6b9d1276cb533c7805a6badb5d666963773d6171b03b62f625619d0784c0b0399d2541e34dbca7e41ec6d6c9ef1e57a4f0dd0387b93f1a576e70dcc9634d20dc3688e912378d6b86159965503c605ac615996455f63a6ec075a94247008e669029313388c3f8f9692b39ce52c67398b17966559966559d3a5b4061cbae41b2535e030feb8dc60b13e7f23c6442d7ace39658c2f74ca1c481b8df12077481933c99c69b017ca73d0298c146e20576aa22c885690197c40828a0445b2653d1a792deb05a1a1f659e54d4a675f57b441185f98dd64fbe9edb836794ccb91dd2fc88cccd937447cb19f71c94f74df314d268ca93d0d281ab497e96b034c83e7a3b1d3a07d1891c0fd90c64eb6df21c660b7a8abefa4b5de4af321fb2e69edf723faf4c8c0c96265a7124a5c9315f490f2881aeb4e8c09414ced88406616764c2fc8344a267ee409327d0f7189c90a780b2d6eb5d5bd341faea5f1202457af327b6dfdd9f8d2971e95af69d4a93f9971008c2df4568299123018926c54e869006c805b7e70189700b131e288fe66621ed2281c2232a7514934aaba8e13456662a63bfdfe6e14b8c1202dab1bac0b593c92e5ab283294e56733738e8f04c850a4c0e1338a10b08c48c8c93503655a9fd1940654b3bc0f3c492650153f2ea8420e6b64f9e38223f207a8aa20c8f2f1d59327dbc5cccd36be5429636f34968d2ff130350e12b9e2c8b469c0e10d93ec39e7677f2a0d52531555745185162dd8b942476ad87e49849cc8550811c9b18a2dd45023c72aa0f045111c737d18b07593111714139bde05458342f2851d23c2ca184685225900395a31a44496da7c26611a25af4faa7c26b051ad6117e098abcbcc50f264923e41de8aa02b7072f4f9f9a24b90a38f154a7216b51c1806d88a9f1c7dac38924369822c1fabc8d1a78a25796a39f012b8afd81dc95dfce48d890e0ee9b4175bd3335dcc6df8bb0d529936657c0e7b3153dc7d73d20625e761cd33a99ca54150d6b8d1a0bc0cb0ccf4d4d603744a4a29c53068016602476c4a28ea6b8c3418a1f000eeafca2ca7c822de01d601a6591659326b13ba2ca17487f96d55e025a3d4100f7c07c1717c390cc1a3779f8fa3d1e8f3a2d798e6d743cdf3f333a241d16983a2cbb48fa125434be2c518c894dba6e5b0dfb41cd794390c99cf80e4bd982d2d87f79d9ecd7bce9339866763c4110da40c587ee57149ae31ce71df5e63cabfd572fc3b715f0f3573e73e231adc5ed276c41ce33b71e7e4ad955fd274cd3b39e6995c4f2e195a52dd7b29e568c66519a5f277c35986bf28656ca79ecc283da687a1e508429ff2ccae78b1418e5b59998f9af66d5e4e895d57c29887f1acde4fd38172fcaacda43ce59f96e3744c03d260cd24902889482f8c4b548e9d6a33303e73c0c8a4e8d21b7dc5ebb8cb8b7199696378d7a567cf61120c4fa5c17a15efe4812e1e8c37a34683f5289e4c83f5266f4783f5279e0f5e0f201aac277942345847de13df45df7d344a76ff1ec62cc3f53033a4fc9c77ce4b7935a9cd89b3cf6f5996655aa6e50832df7dde10db3430775c4fda5e334dd3b26cc39cfca6699a967997f64cbbb41cf3510bd2ddaacd6833b3dea5d372809acc0e441269943cc633eed0a0f6794d45c6dee6bda1dad7f3c2e8e3a3512ac0f4f1b296892b43f551186dda22c34630bd8856d143c980e7e9fc10ebb22ccbb2ece5e117e0beb53670352abcaecbfe621660ecea00cf12384ae7ecee9e945a1fa594dee6da1ff5013b81e729125514a9b5ced994523a6badb576adb5d65a6badf5b5d65a6bade7c880e92bfd583a874c9fc6208b98c76eb73cd18772f0153f4892a31a72bcc20744b24ccb217aa6e5d8a4ace2264e61bbb1519b683ecb32a9e5b0465a8ed177b2b279b74f2d87e8bda3f27a683b8801cf5f3e9a7d1f7b8d295bc7b41cd677eaaf879afbfd19d120f6aaed88f9470e1da83eb7ecb2c7bc7b79ebfde899eb071156be1e3311d695deb4d8f532fb4529c35e7a995096f3f2b0cb4c2c4b8de60641b992fbf4f0314fb3d66566837747a786900e82e3207d37c097a712e3cb6cbb7487cdc7273cc9d798b2fc7aa8595e7e4634a89ddad3537aecd906b1632a8dbaa7526eb237905bbee65e4c1efba6278f754b59a53623b5998c52179621e030fa600f5d7ce8d1a8ce340ac9139228bd1830a543b8b7351c013f81e569830ec8e223cb121979c66fe3bc80fb72cb3759d75430269549c406f23c11324f8984c643e14aae92cadb603d07064c7f53a89683cac60dd658e9596a1399262b6765c94b9768abe99204eed316f201cad1c7a727d3c3e4e8e393d3b2e7fc42fb85d6a541fa1529984c594a974e4999c91cbae497d8424f632220d3e9125fe8392fe0f9becbfc58e823900b324bb26f2f9c9fa941fbeb046cbdfac528c05f6cb03379e7e31547a098b7596411e700d3064f4850a2278a1c91a084911cafe588042596c8588e48508248961f036ed55424ecb6afaf8a551035a42f3918aeeca68fb4a75cd7b65d2b62653bc75ddc7148eab28bbaadbb4eb7cc914e0a6bf2f68db45df4d5907297bdfb7e64177d3f34925743cadb53469b86b27da16934baf7de7b5d2727272760209570de8ef1a9f4530dc9922c69b3597b4822e5ecb338926a48d7a6b89565d9e4ebb2d7b240ec16bb3d36440ee331d946f8c275611856c40a662d867535d75a1b9b9a5b73350cc35fb8aeebbaaeebbabaebbaae11d7087b536f41b8296b593cac7bb45b6c6c6a6e6db9a40bd78569d8e8984985851b615e78b1119661231bccc6c6c66654736b6ecdc5beb0e66a9aa6699a76ac8471241289d485ebbaaeebbaae2b73a3ebbad7647361ae18684bfb557a442525122e8c7e45379dc4a5705f4804cd45c0c8a58e0721b9f469d79e7dbbe665262251c783909c3556012159fb2cb092b30f137522518a8894322291eee8777447f7de3bbaf75e2e685e64d765814ccb62f55505d234d0c28874c25ef6cbe3ee35a9b07019e6aeb0db4e176d27fc0ddf63cc6da2df2bfa68baed3103396ddbf7e374d1f703e597e67bba97442a916e8944225dd22d9148a4eb1a91bed16874efbdf76ac15a6badb5d6c64ec1b888eb0ee35e6b71beddbdb8c1a3f43543c5691491f8acd6ca23ac39578afdc20c2cb1bc4aa4c15aab6a81a11cc6d7d41c4bb60a6a4e162e11e9a3939fa4dce425d351b073bfd7c3a6791be99db7915eedf80b8f390edfed5aca33cedb522ed3d876b563dc2bed877b7cce3b65dd372cfb68bacd880c2cc9f861b7a5685bcab7ef47cab3ef87e9584dcdc958cafd4c8f4729994e4826a51289442261f7dc8691b0bb61984824d39827c218638cb3705dd7755dd77571d7ebbc1c8660ed9c17d6d49cedbaba7befedeec5c9984ec6ed646049cdc945c89a739980abea1986d16a1ef364aee8e9fa0f7beb1710ea83f6cf1548bf4a1f996e3ab909ca53ba8bdee1ae3b8a498565c3b98853ce44ef2eced7f59deec783909c75f8ddafee9b17dec773d5edbcae13fd12bdf32aed87ebdd3de68e2faa8090cc7d1658c9f73b65ffc1fd3ee37ecf71bf1f908c0f248b2e7a783b91178abe75dabbef8776eefb81f2ee327d3d1b2afa6c68d6be0be5f1292634281f6dd0f4fdc4c4a4f451e9a3d2a8341a8d44efee48f4ae341a89eee8ba48a38f44225d41749945582402b2e1ad3b7932b3b3a1572412894422d117ca3ceabcc9fd07fe3d877f3faeebdc894ea2fff872e8003e755aa42c4096649b69ea8d0896b481bd1e93366ed43adbf7764a5febb36fde4cfdaa99ea0b7b9bd73c79753569ec1099de0252bdea715a989441ed2b2b65d0571ecd35735534d5ad5b6fa6ba65d964fabde65e499b80b142e997c935d24d3f3975bfce3dbbbcec9c8747adf1e87e554066d1af7bbf93e83b05d92eba6ea50c7cd1afe373cf441ecdbde89b37733fd1471bc434a219d1afe81a79a1a849bf7d9d3b35a9b0dca4c29267f6ce068b4ef7ddbde8f8c7fd4e4146ef4e13a58cf0881caf01324483f646d13d3570d05290b41c179206edbb2e2c20cbeb9f5a3b494f83d606d50b77e4eb196ed034056e1ffbce8bb3a5933d3bb267a8417b5a515aafea45b64aa2c87612c9761ac9a4a4b51a62a8a651fd816c73b5af1e0f3600ea699f46590f9b8a6cdf468836ea17c4063f8696a3ea9d78d3a0eda06e24f1c5bef2faa7b18832ec6b2f21dbb79049fbaa779206add00770d5415f0b35d86389d06080c34e92adbdf5216cc5d864fa41d007241dd4491a55fd415cb6142dc589e4e99f4662c91e19825c5d521153f8f61227c6a8e4caca31258b44ec354fb7643994320631257baaecc93939db91ab0645158f34787db5791ab4ef23378dba620c914e451c493891ed3412eab8f20c000e70d83b48ac2c12c261f34891ed0360036cbbdb4f9c9d4675b66f28e8f5aaaaaac739bd27b0b533c77eead8cf1bfbce6b2fbac5beb69221a6b51857b6b21fd85e341d438e41d91e8818736fef43a3b4dbf7c8606c5b8c6d5bd9b66f9bb76ddfb6ddb06d2cdba6ddcba33056e63dbd5e756fd07c60b99e61f5fabc7ecae64da7e4cf12fb899344a38a348a66fb09054fb633ac1294e490c5da1c4e9e6c718413bb7aa48fbdfc914032480e35ca5abf32090972288588e49065047686bdaecbe6d8a631824b1677007f5da677f41063fa0d88a948136da84819210b8cd2f5ddc503a9276d782c2730acd20df125c69c744e5ad360eb880d76ec79191b8cf1459b6169b09f837490cee16764a6f1e24bcfcbf437bf97a2b0a35bfad69a7e1fe24b8c894dc8fdfe8e27fcf064eb57b6cfae0912b89a01aec2fa45b5f30aa851d7b15baf82628cbdf5caaa8e64eb3d1a75e5281a75a4513a33b543eb64eb15ce4cd5e82f66aa2a992d96f5aa87061195f174054544653cf8c9d63bcf663c88a81e3c8db2aa85043e1121db90b997b2498e3355de2eeb5a1318b43035ea073668edc8da2f2fac91af679915817ab0b94783339f66e6bef0023d808831dcad99b787336337d7afdaf12a28e28bf59422e0b0c6fde68535635e58b35685bc3aa464a65c5c94684be0ac0e356855a1066d8e597eb50b7a352670666badb5564a4538c015cc97cb75baae6aa753d2a788f58ae7a74ac2182290902c82e40839bc35078985c4480e59720594ad2a875550b62a1c613594ad5fb247a34297207c0cd1280b04c2ba118daa0e24821cc62e4290c3e8054e0e59b28584658204c66436410263b3d6cfd05cab50a3eaadd7a11803447cb16ebd5221a21a1041ebd6337b4d90c058fd7a046008b8da99d6e3dc81e6071f628cac9a58424c42b6be6362975a4b4915e3f91a0888368ab0b9de7a8e362a1d291363e46be42274c8d66fc498ea01e105115faccb1e0d5aaf74a40d084419d6adc718633dac9ac8964a9bc2a018b31363e4ad8c275bf5e386e28b75508b8c6702c517ab5a9f9715812a507cb1ea4d7cb12ed32e50d8afda69d0ba919d6c619e524f261f59ec049237b204b3942859ce9087b962aac76ca9d5e5f7012c6ff8c4968a311086895f686d097ce5f9d5288263661b155a8f41d93af6b02606651660ebf29fad9138b9faf5b0267a360757efd37b8bb68cb4c100c800cf67a01338a7ca9c06eb4117e0500ec93527d71410dd525f7b0080073f3b7cb02ea5f4513f447d8ffa20eaf14c593bab95571359bc8696c0b26b15e1205a24b0285f38310675248726964ec536bd29500a1238344d528ca99fd5e79593c518341423672d6f39362aa3935a9b7699e632482a03aeaadc03882084a83e440d904665af5e71628c0172759b4ed5982d1557857248ad6c86df2dd56bcc9410b18b5c9dfbaa6f5f187f7205c403c2eb010eefc55d597d8bde3a08d328976c5931d7ab57d817adf7aabaf5f568b08a40433d516257c0f2406431e3e433d9a22ae02b6f8394ca1c2b2b65e7b495652f2cbbd6d5341fdefd1ab9a3c1069bf626bf533f664941da5d6bad550ec9b43b368893e9110dd26b4b602ce2e96d8c8831d6e9b3231ad52a907289231a158102c004b636c4b4ad5e4ef3a19332561eb533b54302400b6cbf4a9be972b4a2ecad8ab227ad95555d91c5925cad48a81bca6eb0e8509ed798c01226cf7a14142f94a02c892f284a660a4508c5a2088138c0610c254279dea0135f26f71399c0ade9f0f2e80bfa2532ea694ae0be75daa08d2fb1e27eace69090c4230591801ae47ea40d1214a28c49da02092a2613adb81fee67f40509e80428beccb354eb3b090508e586041423c692186088365084640c2531bc689485220531e4e983fdc497f989f508491bd81551c6fcc49220854b6d0c2537e8c498eaf337288931f41345499eb4fa62e8c4978929d17ae2cbac00980287580ff5b89ef832b725f165723f791eb132f6562fab99eb07c69799690c2f1a9c31967c319434388f04960f6328b9a10c19783125e5e82967dd4e34d503724b6d4625ab008736ce2d361e6d898d0777632670fcb42570a4026b53684c90729045c9066d092be8bc56ea00f3fca403c8d9a4937ab2c2b5be3cc125c71c72bc48a825c72a63b59d68aa07e4f9fd88d639fd8c4b11824c11c2ec3929a5b44e9da993e5ab2fbe884139450895137470294fc0a1d531a3ec496b65d9ad1f3f822cfaf464cb04b64ce0302ed1e1e5185fc25a24db6b4ce0b07ed1d79dc0615ca2cd74beae6f881b21d6d198c061a90765497cf1327dc471793277da12385c4992ed331e1d80a7ebddab27ea45efcbc698edd7af18a3fdf2702882c3ce6c11004e00b20070228ece8b38324d0538bbb6022cea66aaf6cc54f53353bd6511633400647b1c8a340dfbcb0b71f80287dc3bd9bec28127dbe3f0457f38e85801873800657b9d6c4b4338a4b103948a93c64ea364b65a13f8ba032a07d48b3e1a3b9d478367b6d897aa80c395a10c0adc1fcaa10e3d3974e8315981431625216651c2b224c66cb76701438cd16ecff2058b121c705894e49695242b425c96e12cebb24c9465a32c23655929cb4cb2cce680b3accb3251968db28c9465a52c33c9b2932c630287f8c84fa332248da24031e616c05a55a5b3ca96ccd96757ac10533af4e8f0c305d18854323931a11060442989d212a526949e506aa21485d2144a1f631a8a465d33d538b0ce2c922d8d1d3c7580aa1360bb836174608ecc008a312cd95ed3c121d681d1a1dce98ad00a172b4956821ac5ad4cc15be9a24125f1c59eae0cad6861850bb75a3b75e8d1a1a7ce4f014b568674e8f98648d5fcf0ca10661962c939a22d81431d7a3425666c7cd1e18a6803071da9434fa3b8dbebe0d3287c7b1d7e624c16ea90042f05899702145fec3be5a7417b1c74a48d942ca20c7b9ba245b60f7168225b9569e22697f203e327be608f45d37165ee7b83f6293f5ff9f9c208e4185fc417fb87291fca92f8627fbdbc6d21db7321dbfac5116b31c63a37544a2b4a2d4a2da517a518a519a597d2ecda9d39007c84c6ce457f7d65e8f268ec783478b880690e69ec3881431a3b3476421a51644be348b68f31e6393cc010876118c33a0c1361d808c3481856c230130cfbf09106ed714f7cb1bf3c2c4583161f3992edb3ec765ed45480b525707823dbd3d8a10184985a198a2df676250b3dd97ec50a31857156b880b3accb3251968db28c9465a52c33c9b293eb6a4be0b0d4a3e3caa59e06eded0e0d2968eccc54df9e060f8d28681c39c0508c69c9f6190aa529949e2855a11406a531285da1d4bad77c109229ed69d495ed330cbb36798c3353256a9f699aa604be2e5ff269d0bef4135fba977a66aafb6c56ea71010f101463e8ed637696b032946d8c4c3f969cf8620f6a81c395a1ed26bed89e46957c1ab5ddbef41363b2db6fde8c062dfd600030050eb39bccc337f1c58611c81c4f7cb18f5ac8360b4898e06407450b2d30e1312da18952cec9111590864c78f081914cd04fc9c230e10922228bedc55a2c215926b0a0130a1a31410429c05b88d8408413bae9c620822d8b1e9c851496d0b4d8e1b2c0e204d7093b5b16452841c6059096451498147a64a4f073710d44703d4149c6028b659143022b0a45ae2c3a50822ac98f5dc3072a148e5857d0a15ce8545aa060022999d5022501a72db617372168b644db33022620916182c5403dc89179820e9ec2117cadcf144a108020a0410a6da02fc0f80b3406f8248852815772f4910210e41af073f4910213bd041c230dcd0446a94217b8cb6222812d24482db8e04163017339fa706164ee70c1858e501634471f212c7aea10b2134d3f3edad08c9dc2fe4e34a1ccddcdcf2924cb79fde8f4e0bc02fa552eff889f33623b39be4883734696de41901610e105536822470760a0800bc24004212f6e962074cd13549d0e25a856d0a1bfea0a11501a370ae540149d206484dca69b5272f41152c274c12003001638b4b9a38c803dd1c4cb1cbf93cc33cbef442385e4acbdb033d91d813c3bce209d6b22508e5960f007584e2c64d92806b6bb7b04032b519df2052c3fbbbbbbbbdb830196dddd4db120359d9a1318acb1aa39b3a5abaa33535891d9d2395f8ed7d5440289b1c9a443c6e8058d159929ce62455632c7633790657ca9bd015a4bd07989dc938e82acab3181ad5f2d6dd89d1cc13983d8d8dcf1856a20d7f842bb9250c8fd5150c8ede47e761921f7c32b771cd2e1e5f955517a5952ac48ee9ef8d21f15c13d493cd81ab218634c2943d8dddd317224c04670cdd1472827d3243097a38f1007280ab095a3cf1668c026bcc517688084206cd14506d2c25a6c61c4905dc10b5c94e4f6b2c50f725fd37274b6451b323dfdb5b08802153e3dd0c10868b006277041031c5280051a58a0031b542106316707d3a71891517022003d40c3134f709103356011d3d0c4089ab8820d2b50411270d069822f68914bdd1970789cb238a250dfd0413eb9a1901b0a2cc80d8533e436a18185522194e96d900b32bdc55f3ca103991a21d35bd60c4eb0e246a6af6684e10c9bbd7509b451d60c4d7001ae827c904aa657484972932428532700654aa98d5e78810657da4f0b1ce47ec4556829f1a34ccf48ea94cc79acd1ddb485ba25c77e4074cbbcf5c974512401d6557a58196092fdecb68ef5d5d86ca95c725fc614c97e00f78c49252581802290a90a1cc69e9094863cbf03d34ff2f838a124db384dd3250bd9c9217d4d0ccadd56cab2685f627837554969d73eb06a8f920b31a57d4d440cb9de0ca3d7b4f36658889223ce6ca98d135fea2d1fe050fefcc89f0f3153d6f617626caa200803e61a43c4978aa3a489f4555ae5884a0df0d7e7c91d094583f5ed499e068fe8967a15151616179719213844b7543b2d1ee0502ac94a1aac97b55bbe82b3e2799ab3595024099d5c2bd04ed56a9d5249aeaff435c42e55a5239da73625070fe09af968598065b6aca5f1a0d2a82b574d89beac3c42477c306f9b4440c0f2b35bd20d4b15647d51cea64a9629a9c58d1ab27c54c912cc72c6ec4832c918639cd2caf6e4153218f9f9782376b19423e9246f1fcc3c8d98001c8b437b3dcc8c78433b0270401c9af6d16ea98f8d9aaf27754abbbcdbb31bcc6f1e088e437bd61701eaa747679687441c1147cb38b1cf8c7de105228ee95d59f3220e79b3c4b84149b51cd2cbb06753cb711df3ae2981594e9aa9774b85a9f531e654ca265576997480f1a5de145feaab96bd6af7569b3965d84ff79b3a6ebeea5962ccf59a2d1d37631fcc8cf8529f5204981e43a4fc0c6bae624c95e3cdb1071341167fb16555abfec69ad15bd953ac76b69ca2c9de9eb45696bd6a13596449e10096578932466ad263c50a92486215d151430e4d39bc61858e14a6392335cd9826956752e0f0c60477883137a6f7eac68d4e95727618623ece396769ceaad2094e7995374abe5a219b50a4c0b56b95a5215ab72005b580b6a0248771880b3f55f60555e8239da9b843ee108900cff7a58622044c1f3358293d1ddd504a6ba66b3892291aae5882062fa0218a52554318195a020fd4c8b47a7d9819a4fb306431e3aaa9a231c6b2d7d65b16a9c1aa62ab0885fb3052016bc10486c733801c904b88a9d1eb838831a327e42aa7c03d9459f842f7509ac1fa421a51d6ab9107ce967a1b45301b4a0211ac0f63cc7579939d29ed967519513e2c1c1f8d6a9d964044f50a22585f3fcd83e915e8f4174466d75d02a4c1fa2dc7cd93a066599b657196852dabb32c91658d2c8b04447cb1e64f6ca929ed75368e8ca9c6892df5d348b7f408e24f2612c2d348ef541c82b982f999a938edf5c3640d0a1b6020d289181373fd119d8a386a12b91e8c31d5eb7b00d1291fb325d6df1a82b91e20d768841004125feaab7a1f73debab262fa82c8ae1a27b79136d2465a27b7915cdff5ed44a79a48e334184a9d5c0f001ee0ae8d335b6c22aa9f884444541b011263eceba58e11b573727d7d8c31d66b3fd1386da473acb599b5d75acddacd5ace5a6c6df7f85512c63b4b9596f422bd9138408c20d9895d60628c7d556daf921029901951f2a7c16a651280cc54bdfc99afb37a55c6ac0b665627287043e08c1e6c08eb021bc294ccb0aa926c49296beda253d850b75807bbc5ba17d9c2943468cd39b1671886615876cdb8aaad41d6d6a04e4922f4d4212f1a1587600105911a5497609124872cd97a58e1541c619593ad5b70460f36c49255b5d68a0d615d6057c086ac1a54836472f8b0232a210947c8d677983e20a1345e6b671c40e646ec9961235baf11a720e11a8471b0206ca80661433528071b52319940974e55a11ad4a0f51a94adb370435889ba24ab31f2d5b61c69466f0fe1a37a1887aa0f6dc41133c6d0c87d1f7248a766c4a11b4472ef70818943497c4ce094e55b88181344ad2c7b6199a5f180bd58e24416fb5a8c9c753633da54463aa9b5c973399bd7091c56255589b42173f765e5911ebe6910071369d47c4dd23a4ee022f8061be95495c2fa40919b7424c6cc4092bb02f1d4281a3cd260e31ba1dcc73743a4bc9d5876116384861a454daf9567a64e3d240e2b0737393385a20332bc200b4252bca00338fa5867a684c8600ba0280089808a35cc00471fdff4314e3f6a561e8271f08d1630cd21bec90d13638262cca5f5c5524e966373b2d596c0fdc7ea5953aacd5c197b164540b8d565babeeb420aca936dcec93657575b025baf9a0e59e4a456af5a8eeb99091298ceabaabe79c3836f555595a3de04097cc22ef3494795b16f0ec9a2aa2ad5dfcc166b24c660328836b2cf06f18988bad77e3d6c2237d37ef220a6e417427b93ad10b2956da022db9f9e37f1c50738ec219985d8627f3d698528a91065d8639e1c8a2fb6a75becad953dd8a7e5006b2fc0610f2599569216d29ac0149b4332c58931b127c66040d39b3dd3a7c12993746a26d1d2083135397063c60c17171616959e79135f6cfd668eb5031cca9e21111470d83f758a46a492c9890965d60a49b7354102a39800dbc7ac11c9d167094e64691b89cf129264297d3ad543b3c5f6c8245ddf43d3ce2fc4d57ba27be585710cd906a179ca38e756ed5853744bba1dddde3a1263bc1cb248de883c23668bfd08c86cb1b73c308bbc385baaef4433ab2f0c22d70f650a2c7a288bdc74fdbced2c4b645923cb225956c9b24c2cebc4b24c9625ba27dacc95ed3322d735895cf3660e69548d31d94c02c81035291c77e238158e83c17131386e85e3ce711ec76d5a0fcd942c3265912ce5a40223c6cabd9b618cf16da5e3cad5b499e9467952811163e5de7752811163e5de67724d664e1299488373c8c49937277767472269d04a2498cf4f8cb9b1d5e78d44229364fb21a6b54aae8aa6c1d0b4189ab6a269d7344fd33e4dbb41d36ea60223c6cabdcfba37683e6096e9565e1891d8346803a427a8040c586a8b5a324524100000007315002028140c078422b17038cf024dd4011480108da44e6046950aa324865114e50c228c1062800c8008008c90066910008fed8ca9d50a679e83796a94c3b9986ab4bdbc3a5c50238467cd5ca33de5d5f1821a23392fa4f102e5f564811b217c136d8d0bd4af070bdc88c0ac987fc444b3994b0ad187d42b484aca4421a40bfe88b9127c45a8b5c81a6814696e31f20608ac22bf88a406095c317a29b28d504c45d5f2c81aaf4045e9cb486a4ca215cf47a051c11cc8fd88e4a288ce821b505fe37f27414777934dc27aa78d895776eeb210e9860856316b39d20d2b60c5be26b206695411b78874631456a1d667bce0cb0d7635b455da506a96262bf175ca9984671c6f793d37e5e513b31a1a2ef9c1c1871490434fcb78dc6ce41bc94623a35902b72db52b829a356c94e2b9d9ead719a14c8050c3867627b29dbf717664e63ef8bdff76fe9614668361b9efca8fbef1dc165b0e22fd5ef90661747e8410ec9ba50a03bad9497c163d3a9cb7379c75ae0387b450f438049e3dc80ca6c90684fa16d82fd183b535d195eb25524b86ef48dd2d92b095f57f5af4153c6348ddaf49685e1b3335eeba05f262a805f968b2693f2479d7e88a5b2e2778c1956a6317b402a9b2ab19171a54c54333d5b553369ffd92a5d2a8649c0acc4a7617b76683158240ce8ecbc9ea5ad12975cecd077dad2271153b2b5f677a33188b25dc7e7c89752f83b3e1973968b00ae5828f23d2e8a103d15bc6601ba9e42fd6439da32ea17d52ccc9cf751716e1571c0866c709dc98e716b02f83f2fffdbb6c759f5ad40cd70fcc9386a2154d78e5ebb1f080187c3c544f629c9c6e0bed321446eb4626454130b39cb1228353a01048f55c9f7db97c96cff9d222a23e4f3098389083644cde1f889a333f0b0a4cde2a3d39155b9ae6bf59b1559ac4a3fb6272e513902edaf2ef68471372f958bd91bc98306e1d24fef1ab9a9d03c6dc3b4df4e46bc3b0eb542254a67f19e3f2098a43aed14cbbff88ce2af0fdd82283d88d0b0e7e97bb0858f052a8ae70821cc89cda4e243d504da8fc49c5dc5af9bebd7f54606f933d4653ca31989521b320714b0599f36161ebbaceb930c4931cb8d901768d1f4b078eb262640bb28dd25159f307107c03906750dcfa31ab3ff801a7cae0a47ec9d9ae03cbd95e27b3a2b2923042606a0fdda58c3fcdff68e5b6fe38614ac77c9a0e2a56e98825c04dcde69c30b9115dec0163d3dcf2d7551fa9d14be88f09d7a8e00fc1b128bea50bb8d277c261066a0ee31346ccb422a438ba997d8ba8c40cd92e4828717c504af366cb6c5467fb7028a9764b55961bc1248672c901174722c4d63b910a96cd691209e22fa105daee335c1e5f2e8ce886020fe5f12cbb552ea3b75fe1fef21921f36482fd1a0a52a1dbc0e18853baf936394cca357694f979b9f9b5465895e5d614e9236f87c0052106783e5b61a466154a1605049b3bbc6e16476a702202136b02f6e017fae84c5241a4edc21bed1ce7d1cdb1bb55c7f4297c321280323edf8f5ccb999594cd56a633614fa052ddfaa4f79095cdb0a9689080e35bc44889cab1f1cb94d702f76087a1afbf0bc390fdc032538aa8a9ec3bb2b4952d696bd930632eed5d5817a16fc82da426ce6421a35b956bda70aa3ece222d6844b4860971b0df965d58d20708f3d848a7dc19c7fde931617db535732cd6eaa4136983e7b87129225cdf59dcf40b732862afc06f60e28016f4218e9b11904a8d103b04e41990566384592cbe57ebc51ac46d327abf41d88078159313e423f2008464128d71d9103b51782032c05f5eff44808241c18eee3bae4bc66ff003e6beab3852a856ba26c4b7f96e2ff1643fd22b2bd26dca08cbf95ff4e76fd2f9bd6c8672df64c3d4d9d9d44d3a1f12a6404abba8086d3180e29248b6bffb7c95f1a283fd2e27717690d2867a3b35a036bcde3db900f632b9634145d24bb53239d050af59f30a5bd630cf854c1aacc58e35e46e13af5aad2177629b4ed6c7c0a5c4c034599fbd149e6c4e50a7b96c7f1785695904c3658e5b6dd029f952e124ec067693287b8693c4032aff61ed927bcf313a29f17bb4e201e177d404f2b6c45d7e15380dba894eb4144a1267c826b28eaaff8061badb1f0942fe5b04716d5873d997192be15d6efe5ede9d5f41aa2a0d96803a3955cf657c8a6bc3d611b83960267dcf0295446618a45c795c3557b195c171a7749d8ed3650e0f25d0eec7e6e849d64849f33659671041ccddc68444e25d432032fb883c13d14f4b7e06fbbe0ac762d14069ee318be154359a57009105729b31abaaa48bffc74862bf5c20734e609d204a4f754cbf26b1a7818ca692c7945dbd492e72b9c0142e5daa57476c2695db1f3ebb81f44b046a1cdb93fbcdb06f1f48e991aee32c48618152c2fa815c874e157d9f664274f20e18f212cca0b48b325adb5e5773690c2bf80e620d366ba6a95a5602f490feb9883a214d9639ecc9222b0b2477ec4ff72555040de756e32c4dd891f79da1ba92fa55609119890b4c7b8ddcf29ae230fa75157d3ddb298eca15318268e6d077d6b4d434518d5ccc41c377838959023c1a6be0d4334862fc38f669174db6468f4fc38c9f2571316bc0b9569add6f6cb2e7a6b816a5bb06ec922047b34f3a1fe46c72b65cebec721c1d8f2034fdef89a88dac8f9bb561ddab2ba915390f6ed3df6c505a7239c9975e91d15aef868a56dd6480ec029f0e12af3b5c37862674fb4de0fa00446c2f701dd4ef006c42ef02132b11a80ad2c39b527463a097cb5f14b745efc7821e000a1dd1a8a0c2cab67b54705119776ded9c797267a193dc6f6507163b09db5589b0e5365ae996e8dce3dea933fbf1e4aec53cf3c7e3628ae9c1220606047a91ee8c3e8bff3383a72003ec2c309a08c2c32526d6e4bfada3206fa13e9b80e7dc4a206463ab0236abb60e8787c0e64355b53b8fb6514024c9c0d9ef4197e8762c40a82a567c5fb462836653933e369a7144a03f38b6fdda30b1be872d3021c6c6971e4e6973fcae4069ce2cd8ba7424472c5137ed588bec4be2f0ad49704191e2193079e8011350a1d260966f23d8fb7e57f6982fa7eb948cf2ddf40473eeb72b8b4d674f9311ec84a33673210034c285d37cffbfa3798020b339eb64b29147c04c282a7527b170d456eb0dae89beeaab4939f6a511bd608490e13d2f8d3c61315bc8761b93cce4a50605066f08ccc8c2c6f21943dce5fbcf547a522edd648cac02fdfa59182099040297a415a2de9961eac45d7454643cd36ff01353f62e215255113253be8f518624fe9a4c81750f55d7a95f6fd7646ec838d28f39d7136ba6417a878f1f7e940d8dc21d375d9901f400155bb9763f9555cd10ae929b81fdaff0a28c519032b7e4c6042657b8ab46e2c004deff0d3cdcc4af06dce1eccad45317f63e2ef991735a323aceda6858eddf0ad69c37cf976990dfc10f015bdeaf5504cba96aadae325912bc4a84f1609c97b412495ddcc8b65c6217c810cacc147cf41cb8900992f972fe709e25571dde0e4cf26f177c75429d334ffc79e2dae21154e1761537e5311014316687b133649ac94da484a1f8f3a023846d3ed185fef8736f4fc4d418d23c0d52e4327813618506e19ac254d52839cca9e02f915a54e4299552c70135e4971350a0b1f5d3fe140d3cc5d209922f94d418b880d7cf2a6172576fd47aa569304df353ebbf848b920a8f0cf8a2023917027817c491131e6b283acb9e7f5cd02da18935ab5e8a8a37bef98e3987f053f5a9f3c134c00c6d660d04072e97be4a3872e1d038abbfc11ac1c3e74e0230aefa39deb827de710d0d1fbbb01c007c72efdfc59a5b300716ae4d7e50d1198d6c7fbce737ea4c85e1c553a9285d351cdb8d2875c3076d25d0acf814f27b0ddd2ab4b39e997615db5861ffba9faf4b6b8af83c4c5b1d9c4e8a31a2c826239a53c9a3a165707dbeb60096e931c1cbf8998ef36f1c8f6288d7dfec300462e283cfe7f3cba51bd0bac7abd07d19a06c9b452f7a8f2c3039d73581721717385f35398465c3506e65d832cfb9cbca9723b534c8cf4efa5796908c97adbcfa33951a92d7a9acffa090eaedc6b8e677cf91830f7afa67f966d77b18c084521d70f57eda50754d5ddda3cb30133e8b8c4c04af284b84d92e81532198760abdc925e55ad2ca535d121481c30c22f0aed61acc14e825e7ea378c0ce45a078d6210299a7d23e71e0db0cc35e298d06e9e75c28d6771b84e7f76254593af4935c3ace9742ea5a7ac7dcc2e5d21840c07703e8dd4a8817ff857bba7d6a4857c202b458f814dbb6a43cf629c11b7503f3e43b12c6f1193d81424e6d4d44a66b84a34e59f94b92d75eedbcf811053b4bab3b0c0f6d7880cb19e1476d4bf17009b7a0b9c7c83b7cd529b8df31d9bebc36617542da4610a5eab5d2a2548d9b79ec7099a6eb07b549300ef3aaf25d57300d896d4f2975bba6f8b5babca5bedbdd42becd0bb66bd64f41d8b292f2bbbed96715cff011e15c1e0d5fdcc2982d23272e3c50de3a1d83efef20867b820edcf5a7d4a1522d0d806bbe5a32421d8692cbfd6f8552abe41d8f9a0039c45cbc7540121c4529808c9497f6ad983386b3d79bf9486f08584744f092dc2919f386463a1f61b328a09de843adf8638818922d4e5aa0ea3a1eb34903d3524ce10fb8286df383a8d8c38e86eb793f0cc7e759a6b0acf1cd9a92ba95a518db6f64aa91a12fb0da213d178cae6b278f8d51b58083c35f5c488b9edb80e8592065c4477ab9002524b33412e60546a9d9bf1014e41ac3804dd2546edc691bd5aa2cb7d1d39d5fd85fd903194cd9bfb0d9b9171f51c899b0cc62056bee48f8dab276d642befe158da23c9ab00b1890b6e719addd82476a1b2afae70e3b940a72b15a27e8985d3cad648e4ea7b9705ec6cd84bf60a2d28a1c751e1f454d17e702fecb3f6740ca019f7449cf91dc6ccbc6b98e2bcd662b60df8083e66991dea34a31161fcb408dafb8357dce44b423d15679f210dce1cd4fde3e1aeed1676a93bb53a16a8b6a4dd4a9b632a000e10a6083b2973c3120ec9c2d55d9f8d4095b2f3727356d9bffb40e46a93285f23ea21c81428774d656b53281bbebfcbb4df987ddee9428a902905c18543f597aa14c492e6828fcab0f8ae7e8d1e122e7a8ce81d8d92c38df04f066339a68080e3f2f95e590e496e61275db59f56ff1e4b01418b375b102274fd795aef11ed5ba0584c1284b60890701b97453adf565c0b8865cc39f81406e222e12c0dae7a855957fb247be9f3faad6c78a69d3d75d5174547eab16f8f68850e48fc52bee0106b65d61d85bba93085d71f63dbd3d6eeec2e9ca4fedbb8628366924afe965e83813853b03945f0398394b8a503331524471c1811c12045cbd28122ddfcce14ac3aaca07f43b3cf806721361edd6d244d179a8dd7483a4b08e6343bd2673b409e0116815e143d5bf85af8d243b4303d9fc13794bb6645ac5184ffeaede1741c885f525ab70eac22c404dc64bb5dd1c3a7b4a62822bf44a2d192c62826f44d85bd593e8fdc097ec83f6740543d4c157ccd0d160e1208769deed6c5494ae3fbd067317c5148564164435226538e6c0357277cef3e8883ea8a196c30b1646831df7d787a5eb9c67512e41991acbbe6984a46784f6b6b54b4195b541f430473412345beb46f9dae3c789a42dee904115cff6af229a9e488a973e7d54d210c0297232cb0be9e46cb4afd38009e1a79b513a7b406a97679a5acdd18a18b6e2e2ef55607fdaff55c7c7499fe4a381c7008343e486e4f0cf32573277ac0c40e8a1ffac36c6df073c2380c5b128857edf573842face340ddfbb04e25311f017e4c73aabb5e9c8ba46ed19f6bc763f3a9bb7d65402235161ecada045cb44bee807510838d922c3ad6431f6e13953bb92e7ad4215cb16dfa846ec798989e293f7054cb10c7af77f941b3761e145074e4b088bc674ba6236a3727b2dd7182b8abfa4d6c50a53fb28ac9e27aaf3af103c2fc9b7bbf776748177accbf0e5032f90b5124e8b7fab58fd28c2a9403ca2e19390c85300bdaf46d24921109b6a8421bba3aa77c1ffdb2c491478025c07e70ece75465a70049431928a3418e4f361abb15085b1d4f8d5a5f4cd0684fd1bed7e040035fd81db83e7343af293fd474b8b2e0f5a0a4a5928ac0d5af0207d79844b771e6366e404e6f63dc624afa7ac2d400d5382a63bb12f84d0d7909ea2c059d9c129cc239c70ef28aa9e20085d40835a9de816fa0f28ea7444eaf8a36c1601818c2cce707be211aa4577a1128435d764f55bcec9b74cf15cbb3c59546932ef9b9125b08966d652c996ee2a8f30ff715e9d262936f347d574c979ebcbf61e9e2d201bfa09472ab45cc3d82ccb6e1f8c9b182c3678bb43dacba773c002fb47cc2411a3af2b3bf0e8cb53a25e036ba8c655c9ebebd172499b4a381c0f21815440c3ee25fb4105b3e998cf88857ba682e4e10c97f69d85ca025c264e94c9d8b40e32c8c0bef80b872c26c473a2061ac3bcc3d5c34420b5312a6f6730bf95cfad68f56d3d8f6f251376def2ae14104152d7d5d0b1cc823e29e2135f552b7775bb03a6d79f5d77bf59f51cfd503e1d4303a129eac5b4865d6ce8c05c276612d199a098c29db91eb84db9e03b78252e3973bbb587575dd07904e8834b5b6c34eb6cb2ced8a78e73640d132595e799632c83c77e620add0bbb1f22763f1d31fb9f213b1e1426138ce29acb07a7f64619280bcfcd03559273e1e978ab6a33b75f0ab3e74f735962aed6e81721d8299f5f568b035f41140411fba17be167dc9fd6f41b510fbfe81866e00d094b08caaf74061a9e28095ba7e6a71b6abea43a4f5bac8b5eaeb1cc6e6f3c46f2d935b7692c35abd13d028234a7512a4eefc4df5d5da72f2e68c2747a93ebed2277008ea468966f069bd90ae4f87efdc5a94c6b4a2f44ef52c71b5c8eac348c57bab350a69b96297ec0e1ee8f2a16b2e0278716fae288edb51111a1f7c1c2e54bd62de726b422adda025f251ebc3702b5d6a9cfa6000cd4a82ed0b9ce11b5c775d2b317efd129241edcaad6308ea4a2672d4e4723962afe8c6221ab01afd4bc4625674f1a5a137ca7217dcff5bc90e218ca04d0a58c77819e01c74634151d433d959acf7964cac83481bbc8f96c1c2eaa0bc0f5a98b1d1e5e35a139bae6920f6dc577f3287a9cca7b688818f5ec304543f99331cc876a0cb389072640b8f52809e9245ff4b30cd6750171ed8234cb5989c4e5925755825e02f0153e0e34a9423cd79fdcf339856896cc5319c1da0d9245e317df878959a8375151a019d7d9753b5b83771dade7c7a42e1f6ac1a4eca00dce6e4f73c6fba1e4671591af67b68c454a1741a114e2ee568d5b70b6b3f48acc18f40120c0043f5b00134a8c29019dce6cd614d83d20b7bdb6a46b628ff8bfdb6f93a794404cc21e53df3107e1d131b6e20ae72191f7cf9d22d660c77ed8a386fc2ec4e90e5bf9308e40433e3e8058b14d50d9bba6b9ae796db96180fcd05612583eb34432a50858171e403cad1ee96fc9595c6e27474145102b06cadf1dc09cbf066372e2697bf127b922b8f06d6634dd7cdd5ba31af1c2ec3e2faf8600faec10eedbbd65810e28c1d13774160ef4679a4ecedc346c13d118a45edca5ea0ccb73efd0a6e4443337b95c12988cc1e5a3fd6d3752865105db1ee28f72361d7eacaf9b5ae861a180c741320b2d13d9ad8a86b52e34bc07ff73741cde633b3aea0906ea02473e0f47a06d7d84dd2646cf3f5e376a59b9379de1d4d4e7e1475beed24f2a663414db6148cd468da5a622b0b2f9866b69d9fd868e8940611b4c0a2558a4598250e8a8ad52453df728ffa23cb7bc3a492951fd6f4cf3e1df479b5354a94e44ce12030607b50487289df787ee229a00c222cc00dff3add2405b3de149c313b7101df2db846b48f90a6405fc90fad9220c2ec0411457b83e2d017cbf4f75a14a1fbd3952164d7f100c613a29b6252f4517f18514e1bdc6ac21458e75d49b3aadb924a48c6f1708864d0e308029561f92c04c2a78d4f74c9f3583e727bedb5c9302071e795b9d9d2d9b212e2c3ac9eba1c488428e1a3980fb46cf326cc39f200ca5d84da48503bf438b30159536d34ebf6459c79071de5f9db066b925e42c0b373a1273b6217805907e442898c98630656c35dda2a01fa0088d158caa092a7aa287cf5eb051b8acd7d0ac9e829469221a4b35a0ee7b6c482c9b57005dd54d2b5a5999d94a317189adea37fec8b5bd8a5c9d164d3a8aa22e8adab1a862ea245709c8bcab4ed1c2cf41b61834a288165d7a1cc72661b2d53f09ae0eb2a6dedfdff40632a7bd7d37246457f098023d5c5497616b5bd91fd597047bc3b7bf94ba3cad2ee12b0297a2aa88e6c31e4fc0be89548eb25a12d88bf4d087a7e9f61a57459f1515ee30e621d678b19563e1114e2503e6a707bcb284f2304ef688fdc828f4f2d91ee827701b28454e8afc24197677795f387c8f73c3a56f28e41ea9781ba6f02a332e1a276c33c0b58451412679bffe45a83a76634dfbc5796081985ed9c8eb63514f79b6019954f1e1742ead43b0a790284a8625cb3c6db21a9a348bdcaff61292e22bd6a84ac2f9b0be9eda276fdbda632c7abb489517ca12f31b4261fcf62234493a39807a88eea678f494f98bcba455635eab6ad19c272bc76ee5754701c6107ae2b1279a3cae55d5985e48655f6f43ca1a2b95434932217767024b5099a8abe34bc321056a22171fc79d1bfeebd01931b29490e557ded2c5d361960743b3c8531688468f3a01bbf70382d70b2e693d0ea9ecb62bdde77326351a7273cbb9731699e7e3f4fc3474fe7a6a76fb0ea2eaa914a7dca8e9b8fb7b31d817bfc12dce865e0a416941ee72812f5dd3eb7702d28a5ff40553b604515fd46f84fab546f966abd233b8bf5de9d31cac052ed77b26295757cf63b983a2e02a3369a1bc948e2ed80d7722fc8dd5829e9ce74af6101a3f1feac167e5d37920e02305c1ab435b0322a700784aedc27131a353621392891dc4912e820d1b4eb8ac690dbcd239adeeddc96677ef161892809da479294302aca12256e361ff9eef3f858ab3af147fc7f09a67d0d01815d241b392987563f3a6c2a2d156fb71c59d5e878ef824c2cd47e53369610f4fdb04c1edfe2ccdc0c1f1827ff5793a3f8f679820e569d4f2fa42e87c625b5f4aa6d1a4426ea8115e42036fa121531d2f1c43461ffac93e715d4876592a59ae3ed60571f52af5568f53f2ac4da90f31e42aeaab4839f9a8260597b37ec3c88b915b772419200b786e689f5ae9e9914aa77f226622ae2b289995f3d173a15ddc599ada13b6b32a72fcc416a826e32616c24201e70ee82bb0766382328ebaaed82545d07f7a8771edc9049a082d55024196c65bb8d4e6e5c6811bee260cbe931d95c5682203f20ecdb5b9223b86c6aa28bcaf06551475ea685967ff508c46997b885a0cf70361c486e92bc786e591476d1969b37966f03e53127c16fce13749e224ba8dc4dca90f16d0143efd4a6077d1cec8737abcc9cf3cca6bea45c9f52ea65164198cf77bc872763b58a646a09e8989a672db90e5c168f8c6cd204d999b36208b84ea4d695655190c0a3e295e33a805e774012e512dffcce3838d1f5471c24f0ce92571c8cc6e57f02d628466aec0ee3a31b74a7026153b7d95cd23f00433a0f178e214af978b80eea06da5a6a47449a32e2e126be2bc51deb406756cae9dd9becf078a0e9118ef611d0efed777fed694f6b74656fb169e76915ee032263363784b0692266c8be4d6b486dd95d6d2040ef8050d98626624d4be797aff0804eee9a67ffc22794f4de542d8d6616a06e6a2f1d6599876a6d951c8c574527d4ddcfe30af32edaa16816c1ff1942166729da2c02e358b8bde5cf0739332c89a12b9f8a455b34866ff5ad2d0674b43903a342ba3967362e2a18134d348b2618d8e9778fb04e39b1d214a6a2a0070855439f14330410c88880b501a9db7b995ca4606528b99b63c5f174e5da3ca28663361cee38d19df0b0e128981abd21f59e138c2c8bf922abd9de58f2f11630ddc819e81561eab4389e88480adf36259f3f812d3fae7a2934f25f9a933fdea5d96a9238aae2b0f8fd4c39e5ae6ca32d97f199a0b32eac2d038aae54cde3f14dc8ea8f8ab24ad65fe4bdb8b50ee928a04146b7c3531883468836a3360ed2bdd4f0ccfe46f9747b511a4093c770d0f83afcf42b2e0b7d67c01394358b9730602c152417b382633c5695e7e5708ee223f678f444625b53fbcc52271863d4461e4324bf5bff6f23c407a1cacd5d162e729c314b53a1f7f3e4040a6c5d87ccd479440983b089f2b9c87c3f1a62764f8800b4038bfb774183019ffb11fed17881a2988b117efc074c134f850f634450cc6b757d592c5dbb0d6631273e842ab8c40641589d418399cad9918eba94e12db13a1d140830186ac298b02e82d9ed76222b4a3c2c5f460b1cde70049d53df015eb6173cf6e2c8f370916fe7ed9be1ae5c9b236cefa3fd0a54e8419b83081508de00de0591bd5b1f5af4d9b156cb53d40ad7e6da22d939e9e7521e552b04b1b4d53bc158d49ea1725e70a08d5686ab41485efbc3a11bb953499dec42c27106fede58dfab855ab950a6d1b036459b85768d2cf8522d19e37d14581e7eae212b6b7bdb234cb81ead1e517410094f5cba5bc1749ed10e1e69c6bcc79b8f3c68c64765e3b024a39debd4b7af003a92598103b8448d39367699b3f1df6ecdcd0c32966622beb8ba60b92d3212967736a255e50d0eede9fdd9425d9986a36f830be9f9393678882b7f93fd871e546b2a2f509e7bf856781baf0b8346bd9a8cb7d5791b3fdd1d394e48a1312b00d783498cd6f15b4ab5181479102aa9ec3262198603356d0162afba85817186d66b9fa4783d2e726df5213c2aa8f20884dd5a8cf17086c99f9a0f04c87eeafa7c53cf33606d5e45ede3a549f3a454d75799e3fff071fda76c09c4b7662e1a6433a533c1ca800122f6fd1bc494721cd1adc042dbfb9c43f17e3b69fee1da17cb22d684463bb8971db144c37560a1bc8cf6f8de35122bac384d08c21b343f806fd9c0cdb32936f491cbc17ea1bbfa489430f1798c5c521aa76b2002af136e2428206ed8a34539cc916f537c57760de15580d1d985c730a78968200559612bf00a3b42acfce3749196aaf7d1da48fd506eb3ba7d15bb442ce45738ee0aecc61ca5b0e52dfb684df95f20d0c3c1440a81df393c5c9a87b03a4bb99194ff8d060b30b1cf82aa5bf793ac2a1f0c5cd81463e64d2983e45560fa4b448fc25a9a3420aca48545e71b493ec0711bafca2faaf23546ed148c25468e928cc9d372798e97a2989545f3b6fcecbd58f7f9196d23c8de420ef715f0fa65075bd7c2f2a65d3911331d55918da2bc68d308e456952248d0d43f2f97a3ef716f3c5b9ab0e3ee463b0cff157a3a1bb3967ac887fa5a94723be3570c3e63f675966c0282d5185a3e6376c2e80c07ffe017823f47a347420112c07da3fc0983aa4b5bf46b324399959f00cda400433de55265c7d08b2728f2e8e5ce5030f35dc18f8bf596b260e05f1292805e9429495feae50cc30d43f08251191b8aa0adb0cb04b516b52451aab02889260a42479af1e582bd105e833867490740e3f3ff814a1176837da9fff19f80dea8f48624b119559086b8ff1251b110de3c05c5bf2658dcc857997a6fa7512893ca9f9e38e765ab1139ad22410fa90ec82d178d6da5af32a744c93998c9ba132d6e2c3b5c45e33513e9ad9f9f1c173893a1a7f50b3e654b7c39092078e78f869ef7bfaf3941f683da74ce97a83af5ea20704a9cad0f786b35cc109239727372ddcd19ce87b1fe8b83352ae066bbe99b6d708cdd43ca8f8884bd97b10391e3dc66a07dffbd5a0e4c9c5570a78af7a0b85c91cebe4c880e9f735c5e140242cafd7176ebf69fc5ea10b8abf60ca6a317dc82374c9105ba69227fea497739572f6649deb7bc5b5a1c57d8dec4d360cba77cec528c1033030eb351c4a29b69e5e73045af06ecd8cef80669d405e524d4065cd35f59b143773fcaa23a4af2f8f92b1e5fa299781376121d2e9433225fe3379659577d707b246b5c1be4a055cf39a6a8131539368390f0613ba167c5fef1186005c133d14dcaa074cdd472008e1c6116aa8b3f35c16e2da19ed17ea71ec5a290ccbfdbc864bf3cc8a86d6cb733b707c27f16574c5a1750f6a0872dc7192199b49f60ab030feef6c8bf00302bb32d773d7fd09a9d27647ebb77e17e23b41fc427726ca0bcb3b4ee7600582b9faf65d649e9e64e1e8d52e95230b9e8bcd269ab321e9bcabf26f297d1856abb5ba90637d64fe6bed32a0bf32c43d091e8868134af56ca64e4573923ab321361ee11ebd3687206a85e2a93585744b1634ae7aefaab55ca37a1f3f866ae03136aa8bf39eb4c1b27f9f0d68149468a5b6d6145eece24bdcf5861f0b73b739447c1ea4148179a4158f70ed837682e660f49697c657d527d59c1a52a5c364226a283f86d118a6923b002b9f4db9ec9f7ea817044478a1bae33873c5da93e8eeb85a0c86bcbfce11026772788cac7077502836bbc2a77a3d3517334436de0ece702f382650ee228bb68ae8c50fcf385b3b968eccfd447dd16aa98161716b2db7a099d8c9ce37a9b7b22e78c80d6d773b734a81915a7e286a8f9a31b3882381756a630c0c72c3b09caa7c8f9d1bc020192b09527560318de4a059c2d9dda9e9e51ec1eec1320eab1c1c95f738f3dd2f03d50f74807bda0e88256d44084e9bc7d7561d4759729467fc95739c4688b098fe1fc42158cc6df2e2a3c23cbe2ae8dcf10aedb6aa131d4f8fe495eaa4a1e40ffe03fec6da27b347a464bdf42ff30dce7ea15004a8dffa5b636d0d21f4e34802a9bb435df3080096bf0d55b8a39e97699f16257d2a6a77496fab24c891c61d2de8be773b7551c0a1e8964b3a012d2bb30ae4e38ed9406248c9d19e521813fc91753d94b2b511ddd44b841b204496c4fe0002eb06895cadffffedaaa9ccb02e92b834c4be4b2b97f37548bcfe99b46269fb913bbf33630c57a35daeaa6a785b92b93740731d3cdbd7c33e8fe8b68c39ef5deb3bb1789a2f1bd2fc0be4595e19f5b9e203a98b3a676e99847325d861d1608538c201a621a6c03b54889da9111e0d41280281f90b76460b9d67b328a3421199ddea7f92b2a3722e82d66810c45449f6c9257e4b98e6a4a12521e011b3282298110dc5b82d2cd65db6219e24ea3f78d5f57b5a41b8e17680e43f703dc441a0d8765fb0fcd8623e745d03ffd495f724fb6b356b8b6e05060446fc80eaf810c0ee2e0657c18886dbd9f43a62fc35a9f212ac33983be187dddb34fd3464b4163e7be5ee277fc63914956767c249ec6fa3ae0100853937c34af14a39531de329302a9de6eb3ae2860ddc237586c6d8c9cc129de98b1c3bc9c1edd5b4429600319e0c1c7e5604f6c847cdc3d9f939e63b33165d5241813f10493850c644e976125fa81d5fa7524039e8d64e6c8be392957b8da8b8b4410013653fb287a96c1d59b42a0a4e55eef14a8fe808c6fe9e10337f013b35df41a46afc76cd5bf4fca7ba91ec643e1cffeea4bb23e1347d55d30e5e78eb959cf6c639a789ca8769a9aa905ecad6e9e2f204b1dc002b9ff5882ac3e01838640b9baafa140025b9e44ecda825bbf65136769e191477d15a163288bf49f1146e613bf36036b716ff49bffcd13d53b84ea1032c8009918193ef17707668e4fd62d660cb71336ffbe195f61ebae640345ff2771117b44dd0b93d733a14de613426781db2bc42c17ccc4f169cd72d9f4ae2e59a64020c1630c780186518a2fff721d1f4370bf1ba459c307ddc31eb34415fe88c431b1360c3221f4f729680ff7de6d393ec53bfe77290f0f0215e2a5e8eb9f7d5a1ca85e2bffdded0e87670e7d5131874488351f1858589bd8ff8b877815d396095e28f2ec5b6b93ad3e6f916f0d3c3946f9496d9290ef300038522cf5bccdbfab03a49d68053a75a0b5be70fbfc8e36c1f06c7786765abc0a341385eb3e35b1404170d36b6791cd73d2cda9f7b229a34c6ca092bf95faf2574a0044820d75ba0a65f21580e1bc803ceecddf99d013c85791f1e3c34fe0bd65a2b3210c9456385543f61635f7a9b22a90bda4d91720eec302ba738051e252dafe21569fe296880ac36730f728345ded9c44ab39b5eacdddc5a7f19d123055c4ad8351bfb54bafb34b56cf85128cfa2b1872a06bdb4e911fbb5fbaa7f6feb4a4570cdcb748187dd28d8dc579a38ac2af4d2678cbfcc9140a9c2a20f5b8cc95a1ee7017c5999a0b7ef9a4bde833f57134750eb7b160de70a11121a91bc2ec4beaf78312763a39edabe01685f6144f02b30f2ef4d5a8be67e4a2bb024e9910db4173875912af09569dbb563a79644831fb20b8a72f1afd57cabe1df49825f7aa730abf157e2e7fc8bcaefdd6e04800a22d0d776e4c89237605edee335233e9c51cdb42182551f9e8a81d5312d84710d031c77d61780e76429509cf1a55eaff100c47b9a4805c7faf48b7cc938f94600aa874d89f2c0ed0c98a6f69cada3f2629604459a6d32e5bc121522dbefdafedc36a3e155e01eb1890bbd19a9896af8475a97cb0a2f3482619d74c983f8c42500eac8c2ca43f9bec860cb7c11db41cb8b2f133c55b76ac54cbaaf07905ab94d61a417f6d1d9377fe2528068a3cdce41925fda5962c719a3f010f65b2171ecc925924e94c07cb0e28e0a557085155612ab12f2e5c530d8eba0a8442e40f2cc316428be4b3cf6496ca649436daf38d51438a2d7bf53b0dd87b2e57e3e4e994dcde854576ddf86da31e15ce71c35813ef16781fd9b3c2b1c4a72a043aa359fb565fd0b874d2cc61e3a892a29b3f636d0e981f54c3ca736650d42782e1a53c533ac0b9d3f11d3a8b0de30156e9dc8b25c44e4fa0cf67b042f364ab09579325a67fda7b6c88a54264b00ea7b97237ad2476a066e55212686ca3d4491cc4d991bbb6ca4df1b176ce0bc59cc2922d27d6372414db9ce8e1b1bf6360d1da8223a1b07c0d0e95895fb39d61c340d6f247f3b62c9169ce4c09c40df642c94258b8cc55ba144328887e233614ffffd5345ff249d0378ffcdac85801a2e84ff089c42d8ec540f5f0be7d5fdc6afc7b1085321f288e390cfc644347188702b05de34f54f5a5668f607951c05b599b53393fee986184da900f950ae8c7b3101d825c0a28cc667dafca45b7c68aca1a9094b30d54831ee043d879faa77b43dff664b0d7b390faa1f6dff028e9e490b5eb61a84f38c3d3f7d3ad6158e5d5ff5a0c7236296a6c38d80a33601e35338fc6ed5e62ab13d31246b54db228735e945ace98529e1942e240e827baea9603b2e61ec7e3ee9a87d3e2069cc6ceb8004d1906d7a96e6fecb60b0143a2637c4387babceb1f82aa0ae86cedb60f8a5aa009fde67e1c63c7d1829e9f9cfe6d2c03d4a2d5f064b0baf66d32b2461d9fece934ac737dd5e657e3b0da5bff699889ac44dcd87264154cb019ca94bc45eb8cf7ef419cc23c64702ece11205f236f7b64848814a23b2b2c599b8f0c1f35bf97d34fd10e49540a110828d682327c4e7dcff05048368b9ea798d091da14f364074a3e1a7d0b21e09ecab66eca2dcbf95fcc66438557925904980b257287123669bdeab3a2871d1c928300aeac5a3fe86809bfca082942e65748c285b30ba7cc7911f82a2c54e2602398ef9b9e61a31728fe6b2a06c056b72294001f620dbf7ecdd6a8e0a59640a173c8ad0b0dadd77f75fdb055605198acae855f96dfefbcc0c790e46f70ea52de05e4298224a0403cd7ac6f48cba0ecfbe4da2ef75b81d1302c22a0b7b93c90bfc0205b385551c09f197a7c1d3cf742e4230ec9c12f4e7b94c152ac0c0b1b5a2eb67a0a35c458fcd55840fb4efdd47b973c6d2ec254ef4576704ed9ba2841c6c56caf3908ae9051184ef35c28ee756da06b2b1c129d5d832ea4b1012ebd9aed83ec344d24ea8bb93001c457c9ca21b352e92470eda963135349434bc7cf509bf037ac28fb337de3688de8be84b3189fb27b61ff6c62d921e751d539335688a5e93882a8908abc3668724b06fbf8be8dbfb71fa2419010d47bda395a263d68eb75392a63e88d8c807c4aa094faf3c3ade347c10d7ff0cec4ddfb7069c6f107c04f3011e94558eddfc11167e9027b1bd38cbcdadc14db6830ad1d5fd986468949cfcfcd95cf9a896141234b2740d809a207746a95eac13598c2093bd8b7ac3a61ab7915d2851cd5203d3da924f676bac75576622dd73826645ee81a9ad25170c06f1bf5370f8b29b3575bcdb2b9369e906df4df57168f3f3c26f4a9870b7cf9fc762b66f9042e18f57e6f27217ad81c97a7dbb67517b8a32ddc59ebaece2deb97c3fb9bfcb40961ef4371336cd7ae804ba1a789137dabff42bf346363aff9f3653d3d67d3a3e76911811d26a0122c388825fd557710bca64681b82e35d65bbe20634d72f6e27d7291b289355e95806f4a2c2b10c105ad7fbf1001fc480fdc98aa9597f56eb4eaaaf964c7d3054b54dc47aeb04e1562e3b5a9ab8dfa666a2d09214ac5de56317f958eeb8737e1f2881baf478d456593f454f5b06d61bcf16a27d1eeeacba9d9d25714dc1db55febc1111bfe69a9a9b8c40c62ed533f37964ed041412af810a03113be8e56d269527832be87a0c2cd1c4d4d8d336b82317cf39821d5831f86a9dd13f70b261fb55574d3b391fd2078993a0c82eb872284ed243486b04284e324fb6ff1aa0a0cd4c0017e6ab08b3b6701b462582c30c01d73c406c2b2817bdf9c1e658026f4557dd175f1bb84dda06258ff75bb86e3bb6a4a74c7e03e201de1cc15312155273885589925a3de8c1304a03294daa6d74656695db74ea3217d15dee8447aee6fcc3ab58779788ae75ddda28a44f5b7b235cf955be69ad53d86a6f5c46d259de38ecb6f4b5a840c5d0760114bc2701d0d50fc7d3bc532c58480adb92889be1b841d51e84533b499d4314a2265566e4e21ab3afaa447c1422ad73ce3c6524acb9bc510c5446431aa14c90a6538698994c2b8611ffa79f5f1826e18fbbb7cd95c249bf614e448993d9dc090e0a54ff27cb0b3b015a1033f72794d0ca48b997df4fd820ced9ab40b0589b982a39665352a7434d85f7906b134cb7bcf950c651197c7b23cb86de5cfa043428fbe79ea943385a69726f0b8a05928c0573fc7837106d5e6ae4fe377848e999594996d6e180d75d07934dd1c1d54de185cd14e3f6a29d25f599e91ca1c99d8830510c63200f7dc544f74e127ab998c4a911a23a7f8e56bbe6bf3b1edc5f76da256d9934f04687613ded20d13d9d89f1d81309509f0faa4498c3220b03e610bac639fe89869101c44fbb9249263beee46c11c3072d29406c4cee85e7d37b3dde1e507898cbb8e7f02653b2c450b79c8569d59bcd08ec00e87eac97b19f588c7f22ab04afe199e5f98f78b91652f451391015c684aab76eac641b11dd43ac44b179643901f04e9ec0d00e92e2993dcecf3926e9e5939a44aa8ef65c2a05601ef319e2f24820340469783f37d6be5ccfbdf7857b90673e71ade173287eb06048e2634c4928ae44c0865198011c1bd4e34dccb06817fc9c8d129c6079465870f96d08759bdf19842a8db6df4cff03f7e60adae5f9a58b6a0735c3e8d5b498cffe0538f0ac0cdef52ba90152012b096611c1003c2281bbd1c7c4656a042601f30ca4d2998607148b419754a1d3ed87f32dfa93c9c77b4e81a02e6f75579bf12ef608e7a2a38e97013aa14b5778e2cdb31af0ed00fd627e94560ec96f31bcc1832d9428fd4aca0e8fd7b796707bf543a2360532a27d0cc9e5b74b49d14bd8922ddc26f13f217e7e30b907c8352c856cdde59d8ba89fc3ce080698d1f487ac8dbc6356f8ede8a9b4dcd4bd8c49299444442cd840110aa080328693d3eacb42d9c159556759d26e63251287bfe4425285859ed629cfe6100731eba757a4404a1c0b1f3b66bc7460e094aab19c7f212a1d95e65940323a36e2d259507e0bce9e65fca8888131cc60ae8420f0e5ada4fcfaf713f22c2237b1678abda70690ed229cffab97244b0b153b39932fc24c32227d2acbec2226f0d4d681c89386cb0c63a4a6f49cfbc68923faa36accf67026e6f29bb052ab9387522e1792b8d0ac0d0e86a95c74a800316606179354b2794fb0d5998a8269097775e42071a82268285c2792642c9f57f91609d0eed3b6e5015d1f47c72f24ff83d1c37785f9253d00763e58ec0bab029c75e0da54b03cbfca1cb073b48de788391e5c5988adb22d7244d5d8f11f988be3ab26d8ee25524d5aa854478367d62a62314300a518f51e2cea30a5c3a1c483cb203b6dd62ad0fd5a166469ce680cc2494086c9c95eb1f95a1edd4be2add42b0b0a18e0d520afbbc7a046b45811a4d94f02b1eef095788e33d7f071062160c894cbbe93f386a2507964abe5f51cdd1990a529c8b83a1a6d0e203d34e6aa8822abae4de4c7afaa0f01bec97ad046e3bc97c5da2c717831cd04e551f61dd9955dc124cff00a4808fff00366ce92aeb65849c37b1ca368c5126934a31c647244f7e060314c52b7090e1cb5b01772ccf43c37ab13c167f52a0d1da700a55be453d189fb3e83ad405c81641a0263ec6bf5d54c9535c60b2b15513d9af90fa031602e623c26b32db6384a5c1078a00b3cf011389a361da7f54b6d13234dd3b7ac28a7d381cb125d22c7d8ea60a235c75f18f05211897e71f8ff328fb0d69b601e56dd5b8c8280189f541f771e0707e796327aa07bfc9a5a0f5a965df21f1a9413ca1f199b7db3194ca7f7b59e61c1d369c7e984da0010920ac521b6b10fda717b4942350119561c4219052607837fc22ad95a2d96c1ff369aa2b84e9900995a17ba829b4d3ba670a50944abe163e6ddbc809869dd3b9018373e8ddd53145c49184a38924d969560ffb30c431eb40a842e1c5b82e938f257aa7af5d601da02ed7d5e97dc13426c79c0a683a47f34258e0c61094d1a7078bde53c72c61c3acdd244a2873a726edb1cfbac1537a1401b1358ea10d6eff36cf929c40ec645e2eabb224826ea2f840b40e1393787fa5f8c39c80130377ac13bc71012e59eea884172082cfc9011859fbfe7794f8262ca9764f11e13910944b78c15cc490bac568c92ba1afa15d27a051e8910b0809850df23522e829099e84d6b4520486d09a615f0525b57a5d027fae4e5fd331b839457cef61cb6c7fd278775e5b88478b346c6a338ae604f2feb39997a882878453dd24caf4ebb512063f1a98e1bb9d1135d1997c5b736bb8c45285af52eaeb84724160304d02e9c4b02ec48f20db866afad4b144e855847f90fb5190ae54056156aae76dd773167646d0e39a225b88d66819528dc44f042c0dfba6b61d891055f0002b4a7d077500a96de93bf726a8294ea0a4b8de8602148ae9152ed6f8783e3397972e67bab869ac6fa9a841b9b226f6be8f9f445fa94f0650100986c9bac59209303430c999971c068433facc8a1bf68c0a4904719dd2fdfc4bcb71b1b8388ca4897bb60cb064d7572c415684282bad3d601b4f45a32847a2282ff4af2e5fc4f11fff58c880ff976d5f7d7a3375f81c9af8494a7be5b6cd830b2383e6a4415a6883d77821a816f52fdf41f7a1bc3e909dfeb7aa15c048208661dd0c927523d1141e9eabab73cedf1d0ded359d1953b6c2fd69cf565549b51ef040d48687194431321abfad3f702fc7b10b074044d22f3a0002e3bca7a63623869680b0ced9eaee392f2281644d82f29b36f3aa88f5da09886e9006e2466b74c0c7e638bb016fee1298f8193096cec2d7cf4e24bfad15a91146bb4aa8266f6181f543a48f2e8a62d1d7794f2bda23fafa87cfadf18ba48c002a876997068f40686fef806b6c59220abdf315c4ab2264c0c13f824d4874006e5d82da59780c338b6b18392ef7fa1a8119073e5f7be5f09ec10153ca827642c441ddf0dcdcda50443bb1380a0a6532c71406e07fb82bb1f8a8e247e428fad8e46f2d1b404847f6b07820e820fec74e83c89fa3b068c5884ce859da3ac63922ea15d68a2c3ff9c239ab446e520f35fb102c6c09ea3857aaabf02dc5714d984e8768e22f9d2644b07fc5a78f68db27fa492fe57f572c43a38320c6ebfffe3576ff3a263d3c2b7cc6ba29929060ec9a21d9b3730c5cb34798380cfb639c96786e0c5597e008ed9ac973903e1956d8f3088713e0170e6da4afb4b8a27ce62d5e74d140263c817204dbe0c0f1259aeead629a679d43bdaddd45363aa91508584f8ae93a4339e84620eb71b1b3d65dc5637e0dc84590c6d8e51e18d6c4ccf86926cd6a8f780092d53fe130767174a82e369cc2b4ed60998d8fb637fb0187e38303df88fe046ff60fc2abdf5230aa1aadc02f2872b9cf546ff64f5c4b20ea275d867bfa0cc80077d2c896b0f460345ae82d3a34fbddeff422c8faf31285663f3e1d75861ce7d7dd1463aa490a776727086872694bb469b90395694a291a459efedeeef9d188e100ebc669319f0a9b944c425bcde767ea7d0a9688e348bdf63530fbf1df3ce810cdb3f2f90348f0b4e0018c1e6edd1a10e1c69b957223d732c8dece0a05349802484be2f3c59b9602a4bf84581e4cfc18069422cf67838940a11fc6983b778c4b4ba4f1cdd9b5eaf7ae11913c922293c19265ff3198c07d022b8c6f3f34a294a1bbec7dce08d260e8be47b5cbad7b0fb047374cd4a45a6fd4ec3bd093fb1b57fbe5cc78ba3fecf38b17820046788298dc76eedca49365cb01ecd13601236660b7c70d2b703095ad0eb5c5715755e88b1f48ec03bf85379ac20efb1d5f9301288b8f927e670a3ab13451d9349e68300c22077f3d9fe196a16c040bbe0821e1b500ef3b674f929ef9175b263219519e91a7238d4e56fd46704f004b1ff75b9b23d90f8cfc85e415cd3a589fa1349cb76de92b733d020bbed4881de7323a2737270b5450cb19d1b32b8680c6d1129cd00077c0e4ad83dcf0d72d27386ec85e8a2e72bda4e199610fc1fd8d5cb744cf279d4823cb8d3c836dc460c441a0004b1e5bfa629ad7a4b86e39891b019587fedb932590f4cea45221cdd7a5ba49ca12151aec22625567a2be43e5345b0ce2b46ae81431bbd165295cccc0d6ca26400e8f660a3ef24559a43b1dd497cae0558bb1d4293a1dcd4e9b0a41238999c5d2fc7a1c3bb641b5fc72f726dae53cc423c70a0dc8e663f40c347259d09fd0a513ecc56d24e29ad275d56354ce306194b33a775ac19d11eed8e9531cc1f0f598e30cec9cb5c6a145e44d7585a6bc509d71a5d5a0a40bad937a4f513f1b9012bf6993354d76cc93b5fcd4c16a1b1fd25f7cc96b763358cf7ed09c55885b101232b0fc0ec13fa4eabc2deac08a05d8d0cadd0dd255125d1323d30bb96b9d0c086ad2ec7df5e7e699f13e7b46e391e9353b1056239a4acc180a0b88c7317f53951992815da416601d115d56bb2a7c884b27059333708599d528f3214281a570c46a7c3980786f556c486f344ae292b6dedb06047c29f4b8e785263eef636122aa0f8ad39f8ed637f5fa036afb8058990ebc9172a66a5f1d02fe8674033b50b6ea567cd18e2b36f9cce5cb2e80b0c60365fc00ef539e9247f96549e3f6672098a2d5698c1f5627244c81b3ead39243085c97237c182b2c35e07cc47294b5a91286cdd499ee2d66c11d2bc65ce1c7fd3ff4e6d0e925e1f85f68d80cb45767c10da812f230a1acc2edfe5805b86d9ef9d6ad09e73355009a021f13ae0e58a1a36e710f7703caf8ce08ca6ab60cf553bc0af96e880ec41b27bc59a09c4ec5ac453606d9cb9b343132138526b6b9a2dbdec9f45640ff313b2f1e08e9159970f3e27efff38b1729e6bb12a4138fdbac4c1e773b6602f9447cf7f9eb45835ff501b3d252aea4b4573eedf78d5f62d1339be22b4133b18cdb62164422a946fa97003f1b725521eee5b6c60f1edea33da4e4f2cf4bfce4b83511457e5ec81412bcb8802f1bc2fe35f82750c4e4736f5911d4c574440c73fd2fc00bdf353798c98002af52de6f620c259e5a116e79cfffa00541771583056aa3c976eaa5a4d7545614505d7bcc35cda8250b2a165c30fdd2ef08faa7f381ce807db095ede804e0f0ed94ab1ad39bd5003f6de11d26f5d7e4bae01e6bab8b1d58ed420325c2414beb5d63ff64f2a594cb19d56b9ed9f664208885442c6eb5e7ef5874e3b9d0deb1e7ac110392204f9b1dc22d6b8ae83992893ab7dbe6906d7977f1906b1aad3f3d606db70e344b69bf241f9d3858d7423cb9bf3e2fda2d8d5e4ef70c8b6260dc3ac6ecebbe19cb4f2120983cfff56cbf1ec21dd1e1a7a0b7a9dc47e24e33e1c994fa0bc947689baeaaa0afd7337e12fd5cb703f1efeb0293a826ad2b3db157c6841535695b95f00293bad7f81de10eb0e6ebdbb33062fa8c3132adfab40601859131a7ca1de54f905d6b45e395fafa1ed2c4d3bea6b88fb3f506397389792d898c1bb47dc97c4036339608e1ebbfd76b745d989d2acd10244bd7d52fe0169a70b332f4c9131a34bb54210de226891224f01ffc77843ca6cd571f1ae73b8dc6c137ae7ef058164a00fd22e035e3dab27a711a094809cea435847601af055e3e4bdf2a3b14784f8137464e634802a890042cfa904222d937130f07f12695cf0d991aaec7e5b30704e2464f07defdceb36338e0b113351400f8b3b487cb7ef337d3d3de87dcf7bcd6bd952987ba22c373f400da3adf1b59b39c376a3b0f50b66e8f742aaf7d323d7d8464a439b94bfb724908289fc473bfe297784d4a043f23768d159e1ca72c31fe106f145966ce07c7c82be46ea55a83463c010db70a3397d238ceb2ccedef9b1810a0404441d7725002f0a2807a7521d7e7642f49faaede38b7d53ad27930d858196c25dec0112d999fbf799a4b8241d0fc049cf4a6fd2a1af826fb81c30fd6666500a28e442dcb93e6e765440f29c05fa7121eb635c8115d41b5e41f3f4a71fbc24b9f10c83f5391653067b20f58fae793e554851698d45ef3eb0a7fc45a7e03b660fe5beb355570df45060cae1665635d74d9bdb0b43fcc9b6ca0d933c27bc30add670f01c5086946bb5d7db1001d6605a22a0583fe99fe4939c3013bf2e76f0cc5d424fb63ce16b9db783f7a5e1625a6048275a77266be7a1ec2dc6eb41f4114255d77e2e0747cdfbda28942a2ec9e4cc2ba426c4ab60dd56793be5ce434b1937a37faca5ece715ae3fab7f78853b2b37519672ea825be4eec02553dda6bddcb23814ef91bd1e35b694588b310a70a32ccc896b0f875a4655aa8c17b667314fa1bdeeb3a984554151a0f990394eca6d03c444d4aaceff7f59b9552c3c28fc5bce83e25f9fd18a0557decc98ae4c1658d8f996e57391850eb21559053175753f71e8278068cab0873cd958d257dba2d4ef04620f050c258344529adba879a96bd5e09300737f5f104b3123c091c4242ebcfb5b463b4852e98aab1e6e717f02424043b13600c1b48816f7e33387e5de0aae4cdb99b9c136d45166d9500782fb35dc58efd5528be31489ae920af72bdf715848714a72acbaee2348d69621ae121deea958b9522e4f8f62f5c053af38819b718eb5461dc9811dba1333eaacc5469864db7eb8f12aba04fe52f96b71681be6ccb775e846b7d056fc47b90f21f809d8da9a3ea251bb56e511e8a31659c57b04c4fd3d6acd1cf4c382185a5c87a528393c1906001ca8e4dfa791a330bcbb62556412bf6188088d5bad3d62475605fc1cc99742b050522ce3603920a9609a537d750d4068071efa29aac42f55e1d526593f28cd09c48468428d02a6175a372cffbdff7ef4b63578c173d8d72bc44592e6739931c04c32089ae16bbacc0f5a7f7ee5ca45845a95cc848acff2314bf2ba3198b2c5b0e89fa8a87514c33e88d2c9705b2795e61fb7c31b949bc04f71a8a47cb95ddf98ca503be706dbe5c0f794692370edad0dd7c71dbc2a3872429ad0cff7f26a671e6e38910dc341c0f93cbfe05b6181a4469616737474b5946d90f673ad0d00ba0c820d554879ca55089477b54826dea5ceb0a932153eda384b5104b9f9af2ab1b3e7ea2ae8106d24f1e5a96720ddc47928aed969537985c21427d074a430fb403b16243d56511f3fd2fbfc1c522f8076de72d02e892b9f615e4864e5f421fec63fc70b2ac8a9d8600e4bf05405626d97b812fedbab0dd2fa955384d24656662541bd005805f78c2a4290cafa73efd2e062f1ebc9dd1cf4e737fc829901b20212221447cb2530125c979c64af15107b9a8813b89c4bff5b560a97817055899003337952f03d7cf6387f03cf34b065339938c5f29dc45d2456d5aae464f5d8db9a2204d1803ec5929f73659842b8476ab9df8226f9eba7635f9ba5f61a3ad391456434f179d6195567adbfe5de49f8df3c041c745b10025491f4ffee7b92841b491679018a04474aa0511c375e03760d1790b7ce0e8a80c5bf1b5394ff928f0d5d41d27dc8974690bab435860b6936d8d0d336eb5dd47510218c9bdc0a78ed5cd02b65a258741d5b57c7118366db4feb34294768dec493c54b0553d52632ad247f78fd521a04689360a86fbfbe53211be6830a4b6bca248301e9cb2d4ef0e0007857668dcfc2473f6adadf16ac0a1754b332f2ce66d35227ea3b5a1d0a312da7299dc3f4e23c452965d3af390c7bca517c0c3c64c9cb6cbd7f220f8cfe3d350563112f01b5ab0e0512ead5052e04dfd8d05c954bc03e972723b3af31bcd096dcf372612b7c53f7c9cc60055a0b4b96788a5471ec6ba6799efec167476cfe5f77efe09b74b1e8035a5c5109762b26fc400cf088683e8709c3e640c4a89557d181eeabf095d73c8663cf63ee855cb017f26c57d22f29ad30504a15b544549636c69b397d2efca6d2d224a273c06a543860e6a3bd3262a93f7a05fe6108bf844964b156a8af04939344c31a9eec40b2903358eb443ad9871565b253c7f463336cbb63986e743514a93e52344da2bbac2131a6ca0de3e04ca5c2b7c3457c0fe7e619d82942f8d6997b33287a51af251b52d5a7675679961bb5bd83fd97d6bccfd1fc74064a8fe28ca68562819aad17c3ca9b621d3cc1743b017ac8bae3e2793b0a7b8f1cfabfe32267c2a1bd7f3d330923e2ee67a09aa3087803bca29f285bf724970d0828705823e1ed96ae165462de5d387606e34dc79c5da04be66083c82378d2bd620e3a0214b335ef7215ddcaa690d0bedc35848a6d68953d8481cf919f4feee1916d2c074c6a153d848660639207973a1018560630e2a4fc38c1b63b9b043c86c1a20c13273fb450a061c3d33cba4b8109a929a394f9346b6faf25738fc384684d9e8519115f0f6b223e24164244841b894faad611c674b446b1aba283265f01fa0056158bb7fe6495ad671a780df9f0676c3a06f029adffeb8a86fe81169e623966fb88095803c97e1528d18b89819c7716499d051b8d89d9757e47b2e161b7071b9247ce39139382be39afaceddad118721677d83055ca55a6a8721d79490930b6ff2724f5533e5ede331417097f109537666e4ad0bd3a5c9ef6b49b48a4f34fdfe87d4f0a10214ce2d07702879dfc9ba6a4b622831978e7cf164a0a44a4b39a6b61ada882656268e6229844b22d6f88e7e0bc3c7b8e250c657a3d5eb2286cdfa4c95299c590e07d6f5564e5cf24df3be56c44bce82ae26e759f84b54f5ca7348cb97ac68d366d9f860161eb1baa15008002e3301422f947f09c1c4e4a6dfb7693586651a66172469103e4031936d6572725eb481c205122125ff110887feb0f9e005b956a122442d93bea341cd3dc8c080602e1b615dc8045e1294c06765fb1d3ab67cb2e3ee5f2e65bf40853432a4d64a41ad810a880caff40b5d45813f45deb8b03932ecdc09ea8286db4ee88c4dba89c70feac485ec6b0a16f8d538dff9bb32cb1c194f0c3a3be6862f2d5f6ba4943f9048675710a88d8d188f270e80dd68a2f8f8056ee0d82a3aeef6aa57725c0168face139a13e02ac3d6275c3a8949edd52e80d93b10927872461fbb7b1717484635030148ba80b419985a80ddf36bff2a2780ab6821a6dd83a2c5253f772f21418fa67e222971443f04c2b22cbe43aa82efe34f5a14a0d8772493542a913d9cc0693a0c30d8029b71a0ed287205251238d4ab3e9807f1f92f300ab110fd7abd72ba03da690ff2e0eec5fcaf8257ea77de842352894a7c7c4340d1414a26fd706f64c0111e2fc3a7ada6d2d5d6a9f3ce455839d09b292069fa0b7c73c37ac3f1e65a8986c38642e0f00da783ed0df59eb7fee2dfb011767f92683007edf33347c1837af7e94bbeefa3cf81df015b982b21265becf7046ef637c1128209685346d11470a3ea2e3268479cdba49b552d9e3a5978fc369b814f74ff697506c1f9f3cfd92e450ea9e129bae94789607e4c642bc4846aebea64a0198a2f252b00dea9c5382f0827fbe8e9c82024c883ff8a9306cba01f671288369241f3cc3b7b73dd267300863e779230d85d7e4ea613edaf424a0d580c9db58f4a7ace4ba0d82e02ece6dd1915bfe9e8ba39cc1400ce65620f336b40f3280c13b395492cd91096098706145d8b06b7cf5836eaff09234220e8f61485a85d5b5902e6443e211146c62a570fa34c29bfaf14b981b254974e0c43533200eb3d342086d1348e48046a1a2cc86e07dd3fb7a17421a1f8b58956c33cb041e175a6ec813ec3dfc0453d8f8082fb686263cc4013047c190472925fa7a1f0b30445543087594020741f296d7d50038990501271e3954a40a9cdf841fc7aaa84fd1c014f9e7e9fc5e0000ff61b9fb337497d7ce183517dfc70924f3fe9e7ad03103fb967f91ea54c2c0b4beea23d0b71fa20fd3b09e1022ec0e4fb345920ce5928017eddf16a7b5763cffc81445f24eb5f357c39a111384971f85081153414886695e2285913edb8adea217fb6093c92cbf0a97596b114da1a8f7fd29c94c71a32ba36f2f9d7834c5b834fa49b3ecd68ec1ba4b408ac8086c2aaf43142e70ac195e2f307dc7dbfce1efa47b61de9493c4fde43ab33ea086b04370a86b907fc09b3bbb45da028e1a83edfcb190a4cc6b81e18eb8f6d6bbbb2e9b187807a00cd6305bf626c59a8e0d86cce34a7a46be8539ef3af60a5713baa3d396807451584f6dd24c6088a8032c4ac5fb8e6d2db85bae79bb64508859e3624e52792d5011590b5517ee1c3b864b9815a8e1d566783e5b6406c687ae71198330d5fa1c350d2a242ebf998628a8c2acaa771edf83c5e58998caf4948bd06b752ffe0d54f868bdbc2356d10de56aeab98c756fbb511086c725d5b577ec659af960d79d9c325c4eb499d7d91d60d3413b5d60a9876333d863c1a9a6af844242df9dd1c21418b2a9be6f86f56bba8fd6914ff0bcfa5061f381226407b401e510a803cd42d381c141a01afb54e06234ed7ea0e107b4c6c82710ac5ff897409eef31cdea591fcc50199af3bae3e47464704c8bfe810916cda54e2e3029041e9781e4e959680430d4345dc5541e637cdff7a1d35fdc134cd39822d89f78b2216f58bea1efb49faa9506909cff0d633a12f2aa4bf98c3c5711bb08d0c81affbeb0251a189f84b5cc60d67537d0d2e130af96c8037e7597036be3914c6a138a7c53858cc4931ce0b715484e3628e15e5bc0887823914c5a13887853858c881228e8b715488f3624e153d52e80c272aff87634531e7c94467ae9c0d11aa9848db07889aa184fba64e4e59f2c481a67808260d078b06ef52bf5b43d24d3377842cf709058438a369b981e38aabac7467baa047fc721f7d918114191c4872c3f8a63943473c8a1f4e4fc2ca01afda14128a3df505bba6130400d3fa5dffe436459aae65c69451e96fc4af44e3e0abe6ba4853349748455f561ea2ceb49a69d5011704e6ba070706aac52de55007e64d9c1fafb6f19c49920e5f4ca851c0a292d2a25c9c968049371cf1aaa2419cbe4f742d6e729d58e5763a3df11cc313efa07c04c5a4e4b322b3176ca090c542ce797f9466e756b19eea2b4ae2c19aae0a4fe714528b450f1e46157b46b163bb58e4ca2854e48c00825c440d13cba403ae219b00723b4454e2224adb3f50afa4eb452017901548102eae101bdc10dc81359d588c7c6aa3374c7c53cd1be49b9c0206704470a667c89e0a6986c2874e7cfe3d2c153439a895db2a6f1d0d6099e21e21360d0bd96c4b0a5b83550a6d1c9967970bea42a2fa47f9f10b474600668308e7ac906c5835dd5eefe41797e304858a1a33caa0ebeda7800368cec02507a9ba346027aa9db2af9b413fa248b99c6a9bf429a83c0bd1e6b8ce1d5f58594da80139118bf24f9454f53b39126dfa3a74a8d063fa22016ece2de1d1315ca4d82116caab0043b8f00d74d09622029154c10c556d7a9bc3849715b8fa53a57139259e7490504d53af5032e64c74230bceeae163309e2f0bcf508f629231601f7c2983e4412d7c4b59705c0af43f6321fb261bf0b5b4475a0235dac2697d2052b157c6c297bd8b1fc7cbe090485cade0361d13452ecd18f3aedd64f4ac1a3a2c803d354d20abc8fb225d41f7347aaca1dc85415256b540b382cdddd08e298986a259ee56dce18c2792105e7a6f3a9689bc43c88c7e3853c39625652fd17f1705f07beceacb6807e51ceb2ab834d6189decbfda8018a9e9f66e9cc306d08d71df7db9f7c5a8f96847bd8b20b9cb705da0fd1ef2edfbb7d3012f486964d1d84e399c6f3e1ce2c369f71135ca7a75db6c62e3521e5f92228eb4b6e6c682e1bcd7f291bcdbbb614e2d1fb1db1db0981975c6a05623e035542afe0704fd014b5b0194d122fb2877c61422850dfdd40fdfc9a06e84a0764019c0cb26db00013ba169c0c9863b675db0eb98c6793163a640db70877a15cac8f6c3abb39228aa151daa9fd9823c6ec4a51bbb33fb0c6fb367da100d2f4ae0ac2674102105609551034a69034f298009f898c89296f7b9e0420415187def311a965920a479e1f7c3d80db9cfb3736ea0972e106e263352551401b2106e6cf1349bb9d632de87106f9076efb958d91badae7718d5ae5e92a0b1f171c7d1c06d2aa324d742b7f7567721aba62ea8e52bc6ce2948a598e9c195a93ae4a7898493d56cf4fed8a388245a1faa371441590b7f0e74304e72711160ec3d2ab486646be20e6c43bff08a8d1e3101ef8cd9387a33ad733306b50471fdd270976c45eba61842843e6ef9ba4044e1a28de07497975524d53f95035274d4958b8ebdb05785a25ee8606f6fb8888904c4a14f78380877bbc5ec815d41c3b4e145324ac399c8161d44835d1a5e4c938548225311290e54b3c874880bdb4cadb9c9612803b9ba7a692070e97023504ea4bf45101919e6729bff759c4af58bfd17093b6cc4a43d7ec251c6616e496141e93a3bc47633ad547609f7925697de50eab756d847cf47ab9e39f826bd1a374a69ab4ba4c9d4e8ba2ef3369aac7375a3a6f7279eb4822ab252a0317bc9a372c0dfe1f7579475da22f1e18926dbd209a6d22ca6497ab6a1b9acdeed17db5d98ec410f0d46a325833ae09528d1059b351af9c4e99f3c6fe0a28bd13949c0f65620152d28160d4854542dabc3c20ce9a3e1da27292c2796208e219af525e91d8d04b1a010e619c9411c8a48d309fd090f3c71c9909af74a9f0771ff9640cf1869a149654783285428261fc4fb9d8726279000120930744d39ef8291ab427c4d9f7c4efbbc99312e3ed1879646369933f111f83801e8a7ef1f078f23166660d93810ba43dc5cebd5ea3c90fcf4d2498880fcae35d9d5e27007968fe67c8c8943103373980f547d675ac5eca7da0be53002e5ce948f635056a139d30ba0cd01c75d4ecbde045a2dde8154d2eb1324371b91849c1c5a1ccc43a89b3b37261a8e96422f21809a7c63604025eb24d1a2dbafdcb0ca2eb2e8583f37bfb996f5aa85f0d29545403139f9569e98ca93a6d088d1760b6a8b013729cd6f9e8bf7e41283626c4d4ccff5ac0ec79ffeebc9e21b4fd444d33f79dec8041fbb73fca000c0df8bb7ef020756eb0d859ca399bbbf0d8eecaaecf88791e10cb98e2e8d1d17830f68a154fbca502fda34f7b8dc56c9de6ce1c70f386d457c40b741f736dfab9677f2321c27cba5d16888a9ce156a481662d871415dc9d109bbe6cdec09efd82ffa2be3bca4ce8cf0467e86012a2fb7004cd21b8e4d484b625a054176d2d03b57e7f426a57a8d148cf039fb41a014e837a542c149c1efa662a6678d7746255e1b43e7815ef942e88cb6ecdbc8fe9dd9e124744d6e87dbc613e61ae993ee0ffddf0d7dacba34ee821b3845bf0c54f15a5cd14f20c7956037b3b29ed59a0ef70803fe3e7a5251acf291632517e47903975d149d9d014cf61bf4ac1b24f8c2ca1d67065989ebc7ca5bc4f83b15c6eb53132858868707e2cfeaa4e9a9c870bbb103d047c3540fa73dca97352c5dd76cd1af9ae14f1aec8d855644e421fe159ab6fdfb48dd8a56c8d577a5a68d9451bc7f7202175b0cad2ecfa252284b9fc7b5d20ba50a62b3e3ffcbd62f5d0b4f08be6129da5ddd02f940ab7da0f51995b478bf4a68d326c6f516033f7fb3f9effc3915a9bfa744233be262c8c06b312a96cd016913c88dc13f9d69e09a56ded614b4d8c3fada88cd3c06a7a3fb1767c7d00f49b3a0cdb1a648df22f80848b56366e86569cf0ad05c663a845da23e500afc43e3434c890178075a2f9b1125ad1d82cc4fd40a071b4da561ff4b4b10049605009068aea55d4e5ed5239d5ee722ea5e99d943ac39d7f10f6e884c6aea37e15bb80f9c4d8296965a7390397fd90a7be6c8c09c030c469a4c6905ccf485635369959c040420bbb5c9cac40d027401bc0f042a0fd79e5a2092b29a0e1669870fa1f9131b2cdbc7e2fbc749ca3795058990cf755e3bf4a75fe6b5e17d35a61fbaa90a6d6006bb39183b01102b807a2c09d6915194b5898beac644eeeefe6b682fedc101e36c9090d6f6cab6e5de52ca2453020a8709040aa7ff71f37dfbf704fe9019c8a9fb1f3f75178efe3f3c28fb584323124ca6333c328b660c047d23df053c439638cc39270b9d23a75138895c947f61c06310d10e17fb6bb8e7119ee2d6b88136adeeeeee3ebbbbbb9bae5c7477751e2592cc03192f45abdce6eb4506937f652f6f61e4adc4d70a7b5e8d2f672c999114c6c401beb9af38a1c56ce2c415453811c51727b0d4f85205a594524a29ab05984a2453ca0ce690d5024c9b29b35c505d52960b585eb0fc49a7cfc97201964f5dce1964c4724133cd9e7353c19c93b35dddb62bea159488ee54ab132a4e73ce4b27a5f4631d55af5627547892884db60ea55fad4ea840d139bd7b22297dd1af1b37a7dd38ce8acd8a4967b76d4768e9a6b4d287c5023c5fde15963edc3704f5b6ed082d1fa5b408e92ffa2e7dea5fcf8382e97f16149f678756faa12a4dab45a54fea2b45a17e2c2a983e7da566ee5f612c7d3e19a2c0d459e9d35c580bc818f75ce8c361f2c7e6410cd8b32aafa5cfd67d24d0ba2f17777fff5cdcb44bc3a30d8521042d8c343a31b4c8d9144e866da8d68551173490c16b938b26b8232eb6f89210e34a1eb11403892cf34dc584d1a21e1152a680ce4850dec8bef1b7f8c3e00b700f1e2efec823fb69b66a5d18f00ca5b888b4b0fd085ea8bf85ae077023d89e0babb8ac6cabed15bd885b33a6872f702d8d29377564bd09b78a5459abbb8cbe002367600cc92149248d5a13f01bfa5d972144c0135ca47fe3460a1ebb690bf7f48d1cf1a725b4487f5c057191d217822191a5e460207d64967248ac659467c68acc200e1b5242c31cd0f41d5064c65a43e4d32e5729ffe2ec2e93dd41974bd0e5a106a47c971a9052035e7655f655760f4fd95d7a31f265a79e05816cd9c3ebb97fee28f7943b768f7157b9cbb873a1dcb6e0ce9d33d03c902e00818c232e1661d2d474a4086e2ae2b119a5232dc826692ca1f428c686dbaf51da2edb7e6207a56cc119ead1af3cde52fa6248e98b2979ec2d4c4d5d7349ad377770ea55f1bf78e8887c7d2805f94e8f28cb0cc1e0440d5b723bc1a486eca04b8606e81cfa3a1996ef06e687253e45297b98c4b3013ac7415519f9769e835e17f99e3c075554e47b3d075749e4d32743074f46a01a85ead139f4bf2cc176e28620f2dd3c07c5dc1f8fd501b65f80ce99e9c9f46db0f344a63fde64aa44a63f823c9d437f260c5ef158503a8f8bf45535f0f0fcf0f078ccc6d18f90135208c216ca6df55bf2f1eae485aac683253c28fa19504c441c0eba991734a62f50d48003ee024e8ab88841450d4b5021fb91c40b36b85294822586fa35762d6f7fa9c7ddef5fb5eefd6fd8189429d0056976f0f497369bcda2c04edf79d7dd8b73756fbff3890367ee0f08c9a97b6e296f1d38c482ede2146c2405dbafad898b447e129bb77795d0cfbde7e94cba1aca262eee3491422e0eb978a47e92fa2decdcb716c9e9bb97e0099c373cf3a63eae3b3b4c5e15af2c924d76726d228daa955b06cb088b1bc1404a89570ebb593e8ddc62694e1f3f3de4db99947b5bfaac0e93db07a4ea6c7751de8ecc8f3c1c942f03768bee1e52171b1004e5c885329c3051850a2c64ed37a777f00b476f4e07c71632fe091af1728ccbac02c51df503c261b057a96df1fbfe3e09a51f0ba8cffb4e3be88a8a3b5e5e308d8b14cbcd07af7dc9788811a0e88202df2a1521f71531cec85eee2b5d76984a1065cfae23f7153170d0a5062a9aa35ac270118394175a902445d7a04a0e5b58c0830f67445162346713ee26f795da0f5a04c1a35571c41526a60e596221c5114d6481831b907a90c92e5984d1c1164814c1250999fc96bfd0df17904a587e33c18124a05479c286232e31c846dc3904e96004175ef86003d2144c64fd8d815ab7fb444d8b252d984268b14c805d24d05c9fe6fa4133b062031296142031a1856c3ecf8cf9c0e10530b0818a920a6c90cdfff9e2fc8d0a1e3f53a5eef9ba71b63b7d422ea555fe7bcc5730ab59d0e08962c9d431e7ac72968066fb1e60f9f26945e29be7efd995ed6cb2c0a73f6d7ddbf324e8795055425d30b5983af812b28c0c444f91afed4ea8958bfd33fba594e279d3ee53062541efe61b233b97e7624b918e35db64812535c2936538919c643a7491204723ad641304168bb23c3ce5176e8f08d2c67574f7bcf238e79c93d6d47003b9b7393a1ca7102e8de7fa6ea066522d6c23a646b78ab2e69459ad6458aa1a3136702b3583baf1b93c9a1bc2094797c33e076e61ada1afa9c36da4085e77778b60a3e35513829f03470834ae1b332d11b5c2a26dc8d9cdd3acb9a232b5a52283569adc31ddddddd8e258672d27f343ea7362ce39674b4435fd2e0de803cfca6be1ad72d5d66a77e47e8e47eedf68be13caebeeeeee061973ce392db6cdc56caad68d5865bace913d96b686b0fcb696b396cb2a34b6b9c69cf3959bb66ea0e0e865d9366ab4502bc830d945422ba7f20c470a641d1de6fb3be8e2a9ce1ed29a3794d2097a764afbaf77879422d9e6d75a2b923a678882a210cb770f479991d46c24e987a19ca4a53cdd95d2b5b74f3ee79c74ce395759b09c734e6a2d47b71b638c1c078349e79421c2f2e9ec1c366911968f3debc9f480e5dbae9e77c5ff3ae54ed7b90e85677078351ee5c16373431c32d30a61466c89734a996384938d1aa18d116ad4e01419a2d4a4d434e409256ec56d32362b1c191c9bc36500f4cc9973db1a0094ea5049e9d5410ac05c7d4074c70a2b76bce039770460aa00201300954e1500ad127f4035e472c0320094bebc02c002487d4078f080e5a35662e03907e0cd7034b99452ca517a9de254ab85237c893738393b940031ef48536b11227e4144b079e9a8f980688ce35e350028c0c947f8fd808f2347081f101540cf175a889c2074a48949e80852ad47081f0f8d6be6468ff5f1e9b12203d3560fafb7cc83863bf9460fb07c03381929024c98bf3d4de59ee0035004745d0ef0afcac0d4e7677ab665835563f501d14f093c3f7b74723b60f932df8a0c5c5f7e8c2a532aa361e0fa1f50fd5664604a65db964249231b5cae5409f1a982c01e8ed4674c172cb538e54ef77b82ebdfeb1477f7aba15ad5a0a1f9d68035228823783b740460c3c3f3f2c843c7ab060c9fc7cdb27ba4c4819dc0cf41812a8e4af301cd989765077ef1f01b8fd9f70f8bf868c322f3060a71db685c3768eb03aa3331cbd34f8fbae716408695c84601a8c3bcc5fa5b8fb7f97874528f2da9523f8697d90777884704a03e7d1b1fd0b6440fda5d613d236ea38813a0c96380598325b352f9789f1298fe013ceab0a9fa8068c839c1f2e9d75a3f86568c3a0002eaea0bae151f55314330d7b012cd2a84801c432bcc343ca584fb5fb37addb3cd6daba0672bdd9edaef47e7d9e70fd07e3f36d98ee2b63052c070017f4c33076c9798e042abc225089f01172726e022349d28430a3c73058d82574cbc90c510416056ee2b59f4f0c5861464311303dbf04205185b0106563541578065ac30c114650201dbdc57988aa612f8cb7d85e986255804a18685195a6051c3a289c6cd60b1b5d8358513a256ed861839555eef43a5f0274445371a97442eda2bb0e7ef513f36962024d4981e1bcb4c18fc7a922b11b9be0c2246fe90eb8f5de7d44759dc0222e619ea00d39f4520918344ab3c4a2249b4034c5fe29881c1d414c60c87520faf5add451255a3ee91dd3992a84a49e4b18d56911c76c5617d050845b556cb6d34ff7225b5b7e4ba81f269fee5d5c7b54aa61a615cffeb677cbf83593e2c68871974910694211db4ccc0801554696a9a9da1644683cb030f9e1ed20c27b2bfbd620610663429f2ea465cebb0ee97dbf614cf9b793363fe35c438353d8892c0dd9d500a8447e7ec70de06d26af3165acb81a311cf35d2ecd861eeeefe2e4aa734f4a7d6fe58eb16aa50dfdafb8ac1f3ec6feff6b91fc3d62b06bc6deee5197e107746aa677577ffce91364ba794ca1b2c1955c6b76ff8008f74a4bf6d937683b26bfd6ddb727b0deedcba72e17452b9a90eafb7bb9d78d670fa9c435c191cea9792927ae1ececbe75bc72c7d921ce862cb06abe3a6c3c3bf315c1b360cb6e4491026d4a5bcdd327ad2dbcd912d88db3dde97a1f2a85635432abda37f5eb409e24d8ccfd65d5b0d19ab9e1a20901478e07c39a970e1b116e44982e5b145134a49d53bf8a60b690e7cbff8d085f625f89d7df9ab1d3775076982842be9c4dbe56c72bff6c40cb5a37d4e47bc37c3d30dfeff345e5c83785235f1c42be3134f9aa5cf9cadcc87735932fab956f0d1bf9daa8916fcba3202b5b50c991d94ca64231b3caf7864cbe2e55be3431f98680f3c591ca37c77ff9825ebee1cdb7e694efabcb5787cdd786cb57842d5fb1e67b43f31d61e68be3f9eef03ab0651494f98a39228ace1f415bdd9c8be1b6bac7398b57adeef9beed4d0072ff4ccf566bc4050002b63aa78e3318c873d562d7dd2a7c6dda298ab41d9e966d51630bd922c19d332d278a1e6b19edf4084264496b8b48cb52834ab72347d3b21c2dbab305a63a67ba28dde13195929013ccf882a67774b7962d54e2d7d3609aa472abde0f8aea8c06590efda08b1f509181cdd70b0dfdbf8e3c76c2f41396ccc041b05c81b9da1734e491df4c6ba9482a92ca0706cb666581e7778c85d9c74211161318252944a590c39848211666ece5f3e5b5c7ef37f3fb3b673e2b077894429f135ca56fe6bfc0ca20cf9994e78fad97c78acc1d920a0b3356a469260c0631d039f325158f5569258bc36a9e7de50c2cd3073f482a7972331f60c9a3bce20326524b9eb34b9e5fa9ecee22cf9f34e4e9248fde94603b0652dde33f57576e0f65eacf211561e7b496b353dc799bc591769f931fa7f9716b6d4b395255ee3bab4d440e2b721812d427b1d9c861f453b469a6a06315cbad92e97d41a6e34522d33f81d58bc7766864f2867e4523537b9b56b150e15388e523028f7776ffe4b0cdfb0fbcb3dfbe75061eefec87fcce767693b643236d85b699c396386ceb4ed7fb50a94f080af461c12040ad43dfab81821901dac703b600edd343f2740f37fa646a7fe478748f0fef217d48681dfa148a4c7f3c40a6de4c1d75cf4c0a199e8e3a87be38c5369bb4cea1aba6555ae7505aeb1c0abb61bb487dfcb8486d8ec042ddb3cd96e4fc9ec24fe774deb8cc6dd982357720cd270bce7c3f219ebd4f4827efa943c59654e17e490c2f136555ead3a7919cb5f6467171d63d4a863cb6cdee128709398cc975a17d76ae51a697e8da20c31f219d439f0383803e5ca43af4b7d98cf50ef7dd4ea634dbdfc2f0464122f10c62431ff632c9f4af50a61fc4457a8732fd219d435f7e40ae0f17696d1a1009e4ba903b87da3b0b7f5857d04f40f70cf118f6dc3eb275e4d3f7e130fbf48308f9e99eeeff3d954d689ffaf4a9fcaf752476d5e873930b256d865209289853cd4ea7d3b66ddeb5e5615bbbda6a247d6afda17b6c4dfab80bdeab091e25ed49c36aada145fade7b8dcef9a7d96412758f7ddad43d34e945c3e69316e97fe1b7523fe20fc71d3bb24d61ff39a342e31cb292267d6ce835a944f7ccd0c9e81c0ac473eda4c035e943c6a59ed766acbbc82813dcd2096b834cadc85426012425cd05b84952ba75387e20a3d039f46793293487261317e977e05c32672e52afcd9b1a8a327d3a9fd4866c38893a873e102ac013efcc585c301752a39d19fb9e625a8d664cd2e60d7d8a6d38628e69ded0a7292bd8bfe665ceb9855e733032c0a3d732fd49df6b1ea334afcdd914f29a83899959afbbd65a6bed1e8dac2648e70970c863d53ff2c85695036c82dbff91815d16b796c0fd9220b81d0d70ffa5d15a72a42e229b9d3792035db2bf18d78fbe42c312d7035ae67ac0e9bbefc2116c635f99c13f7fa9c20c5d24a8b2bf2afb2751c1037d858625599527d8576640221f51e5fe13e87a80777aef0123e0fe3be13e17ba8870dd735b8874c2c90bfd68e3ee8fbb927c5f92ef34c7529d9a71fb355aa3ec7f2f9eddd0d5a3cea94454da0df588ec2fb34d025330f3c61f8ca560e6ac0ad5a119f3889eb40f8743eb383704a5639c0e7de3bfb383835353e372c9fce8508047ee6b2d73b419eb03f4bc1939a4ecdf7142274ceb1e54f69013626981478ec6c2028f558aec53c908f5db51f6af65748c13aa758f1ca37d94ba4ad13df5bdca9ca243ab06bc29c91e0a9d91bdd5d45d183a143c4e97f5078405061ea752769f634dfacc502a491d32ba4786f2069dc305e1421bd2e484664c4ed137fedea2e5c06a817b909d13ca5ec614ae0a035ce6853e03a552e7d8a075fc3ba319d2d2b6843463a2f8df6a2d711b12fe0d691a1de5f1338bcbf860b2cb5a93f0149a3317fdbb60af088f76559b3f33e6bd8b6f3175064c9917be008fd687ec60b2fc94152cdf1a61234e2f739db32c5f4a774a29a5748e465635dc99f33fcf3ce71022f0e85f7c41e4be17ba8878a80f92852e22f253a063993792d6968677b895011e77c61da0cbdffb20d407c96a44ee7b610bdfdf9f2011ef51610b37f8c26b334618f5813f037c29d0082f609c5db399a062961d0d170d108327d96b5e1c0cf8859705ff90137e82581ae09d490b1264669a4f7fb2bf7ab970faeef4364fcee75770ce8e51d9ece427a15a93f52c66c145d9751cf733b953c8828bb295cab0520695e798788c0256d0c489294d32178e3b3b79b447799c445ea644f16ac07d850c2f799c44998c89659c4259be1ce750962fa514b42c7f16cd2859fe14f258ff4b078c2409cefe83ce91ef5f5a47be972e59cebc6c912b20ed62d7016d9be4381fe105dc7dbb68ed07a45becfed2ee3f20fd2ac2361cfddd5970f1f4a7772f0ef350cee690c34e217631e923037b6f97c097e3565df86fcf811b9060f29d23098452d9bd8c81e5f75269def8f5efb08061c0947da1c52d0e7c0fe4012671b16b5f8ef2d860b2945fa6642927d1e5c051660e4cf60ec8dc8548023bf288038e3646d9479b59eec8c0234b4989cbf15ccdd7e4005d44727ccdd7e4a8a9c901d68035df1f90f9e5cbb6e5000493284261e01c3f62224c7487728037c0cc9b3b64a3e395e3549b311b1da7daeb54cb71aa7da1cf529ab1568b8586c3e8ff2bb1d070d1cdc0544eefd2e4a28d8ed7cf1c363a5e3964eee2a287bfe4a26f9140f8357f3f09808f237411c1d15e944086e3836435a08b48cde7f81c610bf287a62c8864351f24bba71a9e3fd68c33a443b39102539ca11bac1a0eb0653560be61d48d817f69c65e5ff3a3cdf14bbff40a5935bff1afb139c2f61b560d47c86a26dd23b3ffe907b8f3c8aad22b48bf26046727596b76920d657f96147864d5f2833c7e43f30c775cf496e1005b1602d8328f06f8f3c8aa65af31f3af21e4cfaab1c0ec9c825c0ed4446b0825b7c606272fa0758145103d80528b01931631180306318c517df09dc0b71a3cd97c5e3c3b4459647f2b933b89a806ad13b47620c311a00a8e238014d60a8e005eee242225b664995c0e51e8c00a306870610a2d50083305144b54968ccc60c10f9f122c18c204494431e092887ac8a7dc49444343780fd846ee2b5c1c710105072e9448d9420c69832daed859e256da728e41748a7b57cb7594db4e73ab2450eedbe4b422044ee54e2272e135c3d85a4609a060d162080a181ea8280114a2c79eb516b7c0dc57c210c20b914709191e525661833cd2c0f2f8da61872e793a89419e3c3011615c53a69ea43b3b1da54b4db20880f71660fc80ba52a9090e79fec9f35a0093021d68f872c511404823490b19484bd400c34c5604c6d195b4d846df36cc7a493a64c9d35ff8a109c56d528728629e5fade71991c6174a4ce1658c2ace40400d8cba6c41ab52431599bfe8125b28d99fc757d9dddd678705de4165ee737a21d3fc45675a2c8f832b37105521c6d012383cc1418cec4939b820ef3c81473ad2b4153179ec51432dfb12494c5c70830d5e647fb1042cb841862a53575c9f6520337b379dd24a96ef56dae5448d4150e0510a49a1d93fda4dbe5bfc5cd88af5e704f3b4c4ee9959d272b4ab63e26cf14a99f651ac423211a5153f026346539119934c849afa0a2c3db766bb952379442342094b9fe046ac2cb0146adbadce91f3a8d229eeec9022930e3df48866e81bf9724af6500ae54012d137f28fb04964279878acf5c9a3ce99b1564bcad03972ca238fb990b7c82e594a59434af9b35d862c25cddbee91010246609ac4228f24d2ac066d7b69bd2878e4b27f8f5266f946acf7168e80f6b6d16ac4cb93e376785eec1ef9605c9afc6267fa48e35ce6a4b9d1aae1f65a9b72ab7337c1edb93cafb5cc7c0c499a6149f6eed2a3b1b496993cf69247a7c7850457fdcfdbe30d0a1e77f0bcd1fa82470fd504cb12ece3aeebbc7eef81f47b1f942938d2162e78faa0532800a9c33db599be942f7ce1560cb61d3f41767866d74f51dc7ed5efbf37d481d29cb0b1995c257cf7f563a0f590ebdf931455dfe021344b710d51853f6fea8f0003bed9993775270f99ffe31854fd758fe9eca9b3b6d3c1cc9e3a1966a0eafb034a81312a10bf12d8be14a197c9fcfd4b4f9e3d79dee9099a674f351cd5d3693b79f6e479a727a098a1a18b674f637fc125d7bfdce9747a20329f3e134eeffa4133eabd7094d97b99bf7a4412b94ebf93bdf7aae02f6c261452f0c021415630ab7fa43e0bf5bbfb23a27aacfafe96c8410f4bb2989722f346640d683534d1130d693b2a0268888bf54fa7571d814fff334fa0e79d1eec8cffc79708bd0c7b61cec85ec8c205a5e0f0c8bca95df3ee7b374452f3bd611864fb31f3293cc2031e9b0985143c362991d4c7842d3818216ab2d4e3ef1d28c970e431775a3113c242e51542441ed3bdcbca281d885d8c750e6a2cd4974baa54c55c56df9eba20f585d41f220353ae4fa4feddbaaeeb4e2af06e4344c163d7b2a3ea03e803269198c7610b1e12b298971f11d5a7c2160c50c483914ca502531f03621a747a1f0e3bfd4cae269f96038826a0a0916b7237018586ec34b3725d6012ba0f55e8504972f772a3639723c1d1fe26c1d1eb4690a5089074e7c8976397edcba7d9c3d1cb5d7778e5c730cb38b99360d092ef8cf9f9a5baa7d2c7fbee37e983bffb2e10987e09aabfbf721173c1f2a9f717ec2c1fff0a338ceaa21e0587390f8e668e2451a1eef57c1f88e73b9bcd76e8fdce1ee8427d7b8ca7c29e4ae57971a4c21e0c42d99f87e7799e1cc5ec2e1e719867e7624694fd59b85429fbf39852be5e2ebaf7de5b87c5bcf79ec354ef85238d07becec0de8f34f9c39709a91f28140a15feb8e8df02f07b31322ed6ea93c880d845172bfce19915eb2d8684a090827f11234758f0d894f9b35632e28e122fd99f07cf92a1ecdfc3656442171099653c55e8427dab76c01d28505459cf7e40312f3f12f0ab5e0674a15eba50dfd98bf1a1025da820e18f8bf633618519e6fbc6e08e1d17c3ac703dfcd16201a8f072ff92132504a61f683ba77b0fec3aa77b0952a9d3fd0537a9d3d9af019e36af098f4da33f34fe2f17a5496809080813264ce638570dc53cc11d32c0434ef809b2f21a49dc7ecdd89451bae3453db6e34ab1aa34918af8785ca4b4698f067928f1f28be6e537f377288e25f8c7771c267db83863413cfc8162d5034cc7d7df00f14229f5ebd7004c63b96ee1df31d24ec142dd8380ec452c0d478c672b4fa42226a5530a2121d1041762e46e820b30b9cb5b1e3defaa004fa5fb2ea9a4d47441c9f47d15229ebc44ee7fd753ba378906a42c80dc49342ca9d5db3ea0130de92b0268684aeebd7e25d81fb73722ebd71566983097aac0fd49be44a2512997a4d2d33f6981eb266916ee24b3b878a5032516eea53cdaa6b8e858a494ced9a4cf283b15a8d491445cf4211b28f3690c3cb69292142bf933a1869bd4417251ca50d2945c4472d125924f6b1993e4517efddb1f90514a69c4f380bc3794579c0b3d4ba429699c077ce8828a29d278d2032c64feb2491eb9e82d955cc4f3955ca6072c54694869c88552ee4bb9e109577a6d2fb52e6858115a927d0e252941729f3efdb77a25e9855af6242547524a8963c90ff048b3cba58d8935c3adddeebe49f711c4c52aa50f1779a60f1e58005aac5f3bcb4ee7d41d4678e4e1f1af5e7f05a67a48a97e8a83000345971698608623a4908d387b18337c1003252960e85286ccfdc91854589b3caef284e539935c9821cfef29f26cad56414a909098224c1542c84045900c688083232f6088428829b229676bce4994a7184879ce39e78c89028fafd78e2d55a481945d47764f4307d9c31c917d3694dd5b45e1ad6870e4bec2451350ec00f7cbbfdf0ef85df47e758fa42121a594a98bdd9d43bfbbbbbb694fd0768ef47e1a8fa594be4710262b40830947a3c83f7777ffd9d27398ffcabda919b76db750f577a84176e8f6939fbe76ee5abb3616e930f9b6c8288ac3bc3ff2a4cced34457e537ff4505b97af7fe08c0c054a267e530930ca26b3be4ba2eda7d4fa2258358c8515cca38d7ef2b7592d52e5ec45d030a9a4c5aaa461ddb30ab6ec9326e89c1a255759d40120f5768081545cb00c533826a59a73cee92a99182c23232323239f26cb7015c6ed086c290f942dbfa1ab32b0a4db733fb9962e86ef331851c97d650c2bd92677920f48e49bda7e024c49e8e3d504ba482db5dde94f70051264ff215dc44599c30bfca92b58cad0a5908ba2d852c252a86513228769c9144422733f53bed2a3b418bec8136c9a0c58727fb2ee80e67a29dddeceadd65aebb60209469a6c6717f69f402246e0cc852d247191fe90131cf6029648c8590d8174ee9ebabaef8c5da45d6749248bac78ac65425cacdb06244828c4ae90caf6691c46f323add40374ebd0bab8d1b4d21014889c3081148a849ecd429278e67e7b1f65e0ce3e3c46654180369ac473ad78d5a2e192c85cffb32ed688013f7d893b78d82db42bc0a395b4d30c22d05208dc4927d892f1bf7f40b7e96c5a8e09479af1db1f7e662c8595ecc2fad1c82aabbe3f8555385aeef153a098e7fec72d0af0d8349997d907c7fdbde0e713f899f5a3c53f62d57faf7aff805496158e16e370c4f98b09c756febe3bf6d337dc7ff7bfc8f2537f5b0268e4d57b961320b17ddb49f503f5aa14eb391c8e46325681e32aab5e7e4056906132cb7ffb16b00abdb30a832aa84223ab9cfa2f06ec9c7a16a8ea9c027fa0deff03398ee3b8d4cb802af8a7de03477faf3f13fc532f637fc0282370cb385538934e4025b3feb584b957c19ff5a3ffeae7ab9efb152bc4e0676b6362629e7e40312af033f72d607b950a447956f5ad025f4c58f553e595e09ffaf9511709f8f6b70ac7a0bc7a1538caac7a0c8eacf047778eeabb73621effcf8c01bd5f81322a9004d5a35e158e4656f97bfb9d7f660a6cf00476e006567934928dacb2ece752f2fbe1c6d856e7d0ced6ea5bba18230833c0d0ea393843c8729c0482e90c1870d1a176a08b883580114b18c96c68595c3a1068fb7bbb48dea411700ea1f485ac85ee4920a794014656657e90cc821d4877401bba6c0776a18b48d73fd3dace5c338dadc9e282eb8ff6b7d0482b4f09663b37ff986ebf466c84ad749f73ce20597f403a0e0c9251a3548ac5c245b80817cd29f3e8c934cab2b692130d711cec3bcd25ba447de33fca196a7222b18dd34a960ee8d836345b094ac7b6a15a93464e6b2a1b36308b353313d36a6d2e1791211b0fd97f9443db50dff86f3a748c1b2515d92889a45194eea9590a512d952e6fa96e71d163ba6039243cc155862c482667c0021d886436dc88e68d7f906c1b9a3741320e0c927553b024f29a8b3e5d736e431eeb6a1b51148775f6df8e3624b6a2ecbe59c9be21d1362cd9a9bc440e1b6badc8e9b792c3c656a24388780da3a3239165a337c9f268369bcd64a317652a47b7227d5c882cdf87ba2726cb6fda317082321f909c447969de30e1f0b08b88150191cc8676dedcf04edff6f754b75a7dfcd45ae775215ec32058e6df60065a08dee9937f4f92c7ce4e0f1e9e954c0eafeb5eff3b44514655e359d0752022099a8c0343a822f328948a94643b3668324500545869927160cbae0ecffbb6bfde26e746d36ad56089e0dd4ffebd7569c66c58fd6fede230b90d9d68dd33e4525ca24bb4a5b0b41251ede2a25777eab52ec990da2849500419e0711bda7c6e431b158107119c3cc123cd336c0cdec8bef150635096406041a526e340d71432a31e4c2e75d8a43c627827c8175af6f7b12361588afe8eea50b951d9537de328540b70101751d9c70d580533098acf6ff9d4ba30e59c7352ff5182a067297fe5208d09327b08be80e56f3fd2d06f39e79cfe94524a151044d35cfaf89b00896cdff39dff63b339f2fc3184dcad1ab951b9c78d762f20e097f4d97ebe756765cf913bc79f0365ebf85370b68e8723d7ad63bf5b67f4afaf823fb5e10e17a787481e0079850e9346dede412ef707640c33b7d9cd8612f4ed9b96b7b03ba7faf65e7db928c39d5434cbf447eec64d18cc8563d046430c05db3c6b4cca362d8c8668910f8adf9642a4bcf9f2fb65519659e6241fa8e46a420d2d0dbb2501dc493e30c99d3b5fe9ed289758809039e0546e2c615cf9a33cd2e4242846342d59f41cc0526b9247afb1d4867896e0913678432ac19d94c46482492a109de4da8871058c51ee57cf394315e89fb20add33ee04f58fb49b862309e8f78fd3af60093a552411666c214b23dd259320d74e7a822527919953c148d76028d370032f7fb4a7fcfd2da06b30943bdcd9e4ec26960062e6d986d19ff3b7bf144c7ab284080d22db73df1f03b6e7282ee2c73f88ff8e3f8fff9c4e67e03c2f7a33b6b276c6e4b7226287e7456772fb353917c76d398c28a534d3a4274899524ab58ccdd4e50b6eab222d4292c920c82078cbee19b83f22b4084936df415a8424eb8f888b31439599ccf18ccd198451761c4f543652f303b281c3514e507584270b625ee69870b40056bd7f24f416b9bbe00f7ad862c6aa49c1a3cb484acdbb8c66ac267497116d56ed86d18d5af7c4cc7ceb97a60e219c21000c758e1b5d571487c9bcbf6b4af7c4787667a8735c87d6397e9467c09112d162d54250cba3ea8dd4a19494101ef489a205348f8b542845f5ac9ac3464c14028863718654500122207440c51344948005773301cb0b18161a355ff352fe53ffb782f43128f36d026ba6e01acd37a6740f4ff6bf189c790bd30f09d0253c89063d28a51c86552c1d3236b3d6f78cd9ccfeaa409d8c6f2a959a01719ae010a57086c054d6519bb1cfe237fe595cf49f015fe906f85a6c667349cac6666623346336ef6f43e4308a33b4b47a0ee748479767ea9ea52d0e4be1942afc2cafa5737474d1d16483831c780001072c3356f336886c5e3cc9f203195c9052384d885221ced04fd9028f9f45caeb3fcb2b394cf5feafa57b5231dfed8333933afe317f55e06f79269c59377a06578e71cafb1a8e532a9cfa2933a623acc1d8e6a74cc9d6626c06de7e64d5b691e2ceab11038e5d1e3fcb885334e21c657fd668f37c0c7adec262527291a5c5c5d4745697eccfdae2a2bb8e12760a96614461628a0c0e11ce945963e3c80a0e920e96ee69a97ebc19e749fba87a3c80718e689f1493aca3f6f706e8fa1990e65ba04eb651f32ad00336bbfecac8c8d080364b401b2110956d66a9acc211c299b908ae1e83ac9f608d1910bf0c383f05229979995781a96f8139b906a4b216ce921970e5038c2314e2cc585ce01167c8652403dad8d03afe36409b590dd06689cd0ab461d239a8d7515b621dcd988ef76721b1bc66d451cb6e63c4bd008fac283844dd3384d3c461f8fd53aa97f9db9da6e751effb84608ffb14f0ac28ac299e4ad57c4ac7a75e9f4a81384b7084409c195ac773bc94149bc799cd984df84bf3c6ff975aa98712d064393e48860344e389d7e30ce9d0ba67e8fec966d6231c758e3885cdac66a97370660ec3598223d43df8fd637cba8ea5ce711d429d63a3844797d12fb984e68dbf8e17e89a81ae21175d4636378c3831186f1e716636b39112513406a45ca8246d7655512d11d50800000000e314000028100c078422a140249ce9b2ac0714800d839c40724c170a84490ec3280a4206196308008400008c3184c086b602722f905f137be5a7470e736967ea9711edda4f1ff7498eb1ae9a57449fc9cfbe3886807955925537a29cb78eb8fe54914ab60b88752af6955eb0dd38e6709abcbb9ecd67e24015b2e91bf1c6a1476d60444d8e971a1b9e6ac03911fbe549b3fe9ddbe3394b23a44a32e9336e4ca5af622193885f578e5879b4cc87882dfa9faa00e889c1a627fcb878deb74bdcf884acf769681f9c300b4c99345871bd8ed270d38fb3846eee4b12dcf2b8c3ee6ae0502c3e58dc79e79a5c7e24d0c4630e8266b2509f6f2113cd082f46dd7324c1fa0ea3f9302cacfcc6f0af2e6f9f45080e1035b5480d74986014d0bb8453be0a2ddb9949b1d37d4af98525af429313472b326a03d0a0266a0589c50892b435d2121f1f1ec8866c9d0fd5be8c14e928569dcf06be18db13ace3a1c0960e4bfbb8589956d2363d920dc99e796e0b87826985c2d4fe3b94d30db07ed52f9169215b7924b670ac576c357a511e11f97044640edd64af70089a748243f3753122a6722b4fbe33ae72f41e2cb7fc2791ecef5dd09abd80dae3a458a697558c4c9ce36d2cfc301d2522041e93ebdb76f629cb9dfc6e13891e84b3353250cb97fde514104c1ad7b86ad56c59ad85efd4724527487cf16621cd0ce9145cd0826f4554e0732a96b69f2ad0fb4ffa1048e946b4aa4096702b5d28e6e57334cca72c2f22bb4330b9250c10373597b16ba42026ccba9f248e3c66ef01c47c01b384728249d6d26761ccbf2b6c52c4d37bce62a5384ef8b1e3da751288805b3e2fbf4de3e892450853a0806d1e386eef69d39f001c795b029cef5bc29bc6e3141b683a9fb658502ad6f5827e8f4e9cb447a30b0418e3ee4763fa37f8d9813296a6d1a61fa1ba19cacf00b86825dc550ddad46b7907525a96dc7fba1c6dc4e2fa1d49945575498e447e71db73ade13b3c30ee562f3f7843df1adb82c741c80bce996ac33d3e2d21e5cbbacde446ebbfa23faa7820840adf6c097f842bbb3251d7475de715542c1cd96d980be1cf25985b8b2e9802468be58de71b624239a3a4a3ccdc1533e589b6175e0031c054fe61166cc92baffd15d57cd9432bbc51449090ba5b04001ce4f20b833de56042224e0e145be7daf84b46c6d1506f522a0e90dacbfd0927a76fb8a4e9b1dccb82379486bb0605f744c9aee69947bc9554d288e2cb38fee432ea106e399538c9aa0584ca5a3bc953c972b019b9525c72e79a6d4588d5cfbf33fda19e03006ebd1ca0e8a37de01966a0f2156c78fe09934215c1459d88f7667cd37f6d9d30164b78d89f2269a87810cc979fb19aa859dfed2eaa092d885446c78aa8b31b9293918c90888a3fd84738c8b535259ae375e79405e70aa1287aa2ec19b55abf696ed512299b3b1ee8973c069684df9141952a129264989dc54df7cef54f4cda4894e6a14ff817bb5702729e248a89fcd66fac019a66fc8a159536c17d5051eecb0fcd9b96d03fe438c182d008e6af5d78219f628e0e3f9ee9c72f56a5658d0d8b48ca8b084b31143059b9486b9033704b291204d9072679c783482242e44aab0b118d8c2a3d6f03197886fd429de824161a30e7c0d29ba8e986a89634625cb7752002ae45dc52ef80adc5276f3f241391ec9506d4ae6a30b7cc2a6a2ac633d69b8a09074a178b301fb06bcbaa993f9e9c7e1d057b192d4e157e76da3cdb744b56490dc6eb4f4adb5efb2c12cb1eee705e70e131855bf461b67ca8f4b80dd03857abc75a20a8437c805810e0809ac575717501324b23edd4c50950622069605a22813816d9e9a81bc65109b395e44024883e5ddd3c844159204396dde181ba210110d24b6c6ec9d6f974d6537e6fd4081995dcaaa044c334f5ea78107de9f8145f89e4ab7f63c413fed1a2df7c239af964448d72500c501000aa1e78e3c4884c96d1abf14ec8257f9f9c06078c487d9996b9feaef631426801565bd423ab942ad2ac5efb35aa4f117c59e675aca388dfc83775246c0645ccd164a909897d9fb80338fce430ee19d9313bb21360b9d49e3c454d50780801480f014e3f09191211c5a2a06d811d0911d4a842f9ebbb2061c387825005461dc844d3f482b6d4d03409dcc5d5f4a35d8a4c6399d2a74ca6c5b58ea00a7b867651f16bdf66e6f08f10991a910ec3c6298c3ee0e466fb8bb36a6b2a92561ae819268977e1b7276eb1a96b87eb27f15ae87f3396676482ec9b1b2dea91b2b307f562c46f5c15e8a8260d9474c6263e0f297d8a7d2089ddde27d640bc69958de74efab8e14a6c2fcb694f30c62d7348019e5b9cb4c935ac0b4c02b6dd53a91f7cab87b562306b8fbef173b6434d3f8830baf78727c54eb4b10918992a2b1157835431902a9d4e9f75b60a6f3cae252eee8c66d7c256a839dbf045b91b491ccc3ac91b97b78b8da9941ba5b89ea729d9f7e4ca31df6dd25798cec1259dc4633aced19b3757b195010210db8b985978a68512e83c20fe9580cfeb6ae8a04c243ec404a2234af1ea46215d22dbce5a55fcb1ec636dbbbc8118d24d9127f1cf4b5967160d11c2946a19455ab38452f5edf182794040b21854105ace0e4223b3950ced401f11f90a9f0e3226dfb71947a02d7672eb4bd0c6e2f6bf68bae1b1913309056e81b01fbc55dec02692977b0e7c9fa9d522142cb88289270b7ad74aad7b890e28e17db6a434f24120f83f299cad2115e62e7777ef652c3caaee4506acdbe8d412b09c617b06ead72840de019b83d44d89ea5ede38583cda42831c393ce99b8e739e03a2ee05050be466f49fcc3a08f9f058913a03d016dd0b83d8332eb70300d8902aba176419af32e7fdf40337c6320f8c7be328ca2dad08b2b888e6ebc0e6da9e58eff4036f6e29a1ae93b269d03f5152a40f7b3631b8937d49faa31a05266d55772726382a692e7d4e100a7ca238736f688fb752d6d6ce7dae6dee118dd5f35ff824c1be089e799c5f4344ffcd96902df5680a2b2662915378c39660355c44d5df24e8e44f7176fff91b71d4deb3912ec939491cf05b768ef979bdcbb58db7e1636f7be8c931f67ee9861aa53e34c2e1fd99542ff791fa73b31fdcfed08d1ba8c4b21b760ab86f4e1e96fee29cc95b8a61a684a3c02b1b0c3b044c3a9af8f0382562fc73c4ab1008f889fc98df2b4efc23ceefdf32010e7186d2278ae257c3bcab35b86bbbb0d885ebd64c4cc80d379ac178553a1e35cbfd17ba5baa9aba83ef5a7681c1edf2e269cd775795c0d3145bfb70476c42812ac6d34fbd7610f0eabc921d2b3bc65e5bc3043abda685a18751010edf394ff0a02953db571599101a63cd09391f0d40af4c385e2741e0ea9d19062beaa96bac6d18f49e2bd1edda16766e6cc173f884699f682b72d0995098d0ed9e628dbe147eae9b99e6d20e41cf122557522d5bbf61be49b0cbb2ca50cd6b9c8bd46892c14e082b4eb3ac18550704a7fe94511281dd62e9cf73b9e523a0c9fec6083927bf5f35d570207934079894113cfde37703f702ed24dffc845015eed19e01d3547ce3e970e7539536d81f304d70bfc49eb52a7ef068aef7287f018c9c37afdf4ff9fbaf69544d574ba9cd80f8a0f152ce75d114fbe111b41df44c44db6c335eee0f37b956b67ec0cb5953a45720624237bd5e3392273b4350d0230b94fe96682c3016093895dd8ebfff25381d1722c02cbda80f10f93b00008ddbbfa90dd6d9989bd07ef52d8977d424062a41998bbe4172ad440ed3c8dde77d746f442d78d97d4891684b6e8672511d843e8cda0c6dbc659d123ed53cbc2f4ac375c107cd0311b4cb39adca322db1a89f42c296b7970638a3cd88e0db425b270baf645b2c5fe02198643bb371eba8e5c761331e6c1bb92eb76f312e406b41a47f187c694e0af7fc2acbc23d737bafecaa4a4c365c755b78dc62157a23bdd99b3d333dff7c6fa402efa8d2d03bc09ee9be0a1a9ab0c0826ab449b7bb80e8834fc25f8a7e95287efa2fc9c92e8a7e557caede99451e7ba07e8006396e4a950795f29e395903a7736dfe10e9029de557e933e5740818b860437f13d3ee4f8ddae8c712a65a934cc9bbf004b8a695e1a31d2269f0a9cfcbb63f6f840e470bcc02738864eff62ba58ebca13bfe1042370734f4c9ceb9bc0533bbd12c863cc9aeae7fdc1a7be5bb6635657eea14b29f4c6768baaf9a19dcea6f0f703ebff97e6b3935426e404fff442e4a5592d76dd09848953da1f6d6335fa7587ce91e75b67f8a2ed39092b1e5a763e701ec82ecdb3d00f0c30322f069cb6b07401943aec782b47ac9eaee009f76ace58020e245c0d26e413268a823354b6f40fe3e433c09138446747980cca80271baa726076aa80b67b0efef9fd6fdd472372eec55b8516b569162196378dc9d3655ae1855224b0061e648f7369a31a6179cc7344aa17e913517d1ad49cebe93295b7e9520549c7896ec098dd535ef4c5ee36305ebaf1b964f8b66d8321211964eb46ab6d271077b19562625acb1a6efc17a44f76f5356d68abe56fa8d6c646bb6d2f38dda42b610e975f0920aab86793100ae6e5475e4c879ab7bb69e0a40034d9c5318012059320012de20bc71c6549f229e308ce170c8c4268443763468528870c424022d64035fd6e6e85277eaa195063fc371548e57358c4d5a4ce539470771b5a4b7b173a7a5df11e771c8b1966619feee80a02b57fd46bf0b14f8a16fba8af8362df6857a96585883888be27df52ce4d3c30b0c8633969742038c4c336733cfb028eca631898caf1fbb0c216eb740db0078e1063bef6582f3db739f15921da9846c4c4c238a80c5598b23f0d1d261c9f9156c8fcf5c4a20eb4a06cc25b5ee803604969b4a00bb39d510a4307c28111556e222c023c7f3642789cd0edcf17a1de0912b028567bb426151352de3a04edec2447abbc9a4f2118d2bcb809a0ee92b004e593833a6ee379365114878790d805a3f4dfcce0d38c796b78f022df1d8e3bdc6ec899431913bb66c4398cc66e27fe48bb87ef07dd89e993fac7598a3d599da10c57b8ba6b5bf178f4f13842573ace87a1646eb26f03c022bd49958ef14b73834df9c62ee813f72040387b0fd94f591e32e5641f6ce2193ad26921d962174c8e8ed1837504a90af669c33c2a5aadfe300e8801458b3b7a28b4c850ee0bcf9878958c911098973c366a8f052e12e94caaf8b91d4443391052dbd6617f91271dbf452d05a33b7d2ae0b4866a433737efdda46d16fb931f166d337552b659ae2155f067f2ed87496be51805ee2b3085810df78be40ca7460223e6f4deefa69c7bea5c8eca9f51512f9a83620a7e5b209bd5627ec9d64800be0144b1eb8b10c0896b8b119132afb1f51102b5782a5a7c960459f44a172839c923981dd941ed83ec63f1c3f2f7e755ad19bd1b74f7a8861e92f3bc9e179c540014c106de3ccee0a517c83c6d4c45246a4b40388ab7b1986700aa71c2af572862c0701eb909922742c6555a48a882323c44c9ef3c9137e027ebc0e301946d1b54176e6a58a01dce485ce8fd8ed5f830f49d5c6f61550fdb0f46ab5d10a94f0816dde8fc51dd831dc323b467f79210da67d44de33ca0f2ba597f0cd09768e453aec883810c09c2a3884372b025e3f48f1ddbfa4edeef9f188487296cf78097c25890982c743ef96a93a33a638223309bb2c8e6ca33d0c3b1d48878b88ec36b4ff6662c409974b56e676f2b7d47ca447812f1df15d5f92405ff93e6bcd08c8923ddb8e65712726bff0df0e4a546b9bf087308e86f92e6540d83d4cd0666d25bf321960ccd9c2684c9560002d6ea27942f2aaef19c24bd49dca9fd48927d2961f0eb6c20b07c41d9503e55cae30bc7b9a46e2ed584793371173d35b23102e91f2bb88d25361ffbc733ccb66ffeeddd375348803ef3decac492eacef165d803c8b66cf6067c86757c5b4f9758b68f41e7d5f8339a44ebd15d8664409a083f9d96900d93971f8b6787833a4110d03f69c4b88799492bac2f1fe7bf8efe7fab3cfd194708cb146099fc37e55fd4a9777a372593ddd24e7bea812a6584ad2ea3c33c7a9053319ed436f2d6cbb6a7573c6a491b975a04c08446ebda5ec520fba2830b2a16f3a7f6e5d218328495e383adb814b059c553ac1b45d17ca1aae290967b3583343726620d2cc68478be8eb39a3c6bf1e186b53a5538a4b84a9e9cd041da5a79409830840a17c842bc45481c032a2f716078b30bd8e127263fe55083903c749dc52a48e86c60937a3e3bc015d10400b3006c528a55e0c529f0aff3d0d38c1a1c9d2c914ece6990b2eb731abafaf744a19c995221b4d51b5f870540225b55c796851a2f295d4b2762814bd83142c2cc09ac29903d22bdeac7ae74cb34414d6c15e870052600e281493b430d9b6775b0cb3260844de84c1e8e0b08ff4b132484ad16da9d58f060a86eed50e2908d8c24c7d1f0fd5728b3859e076c26212fe43c26db85c9a7c9bc090c4af9865d28a6a5eeb367b08d7d81c178324c998b81351e4e8f0991ea447eb9d63fe096e989770338858e2339e50a1594fd12346d19b686047326a489efdb7a553966b413785afb67fe93316e445e1969401c0453f71862776312cefef2c645d1db88dcf0f503cd1544c1553f33935c17c7f5bc4e47a9c39628e654238dda6cced57bccad93157761b85b2dc76e82f720ff7cd9af7caf491442d1e949d4a38062c4b9e0d6016b218122abf3e7d11dd1a2167f017bad834e9d46e51c3aea6a3f529f58e494daf30f98b2d9dc5a9fff2114b935e952c3229ac47867a24f331b3e61e6e691166c8b6c769f5c083818d0acb2745a4b9692752bfffbfad4aae9953e8980cdbc6daad9134877b704c61a8d653a9d482be474e8cc0f5dd672ca1f0d8615c54092065ee2316d16ce29209b241d5266fe6e763f4251d8d9496e32ca47ce0412e499a3aacd83d9a30da96f6ec2c4083725dbb807cc282c7de2118f93a92c4975eca0e1773f5f8c9a76fd46efd5b6a452a1bf9d7ae4e9776d5f079841eea711af2e404abaa4df33e4f2451ca4deed5e506e3cf5cb4dbd55c16631cd7b6692621f51375a64a3ccb1b2a477e0aa03a0d1bf9f7fe3f6c12fcba5bc391dadea018e3b15c6e95a304751cbf5b1d0b09f1fbab51197b0226f3452b4c86ccef8e7ad604adbe534b995585925210cf276c6a223ed85400ad12004a1a89a8e5cdacea0d44d804f481f48ab8152582505e9802ae7bfdb08bc9ecfac3abe9f622e07cc0b3d7620af19cc4442ec5fd33344858771004f5a58629ab9b2c6238a53b2101ed0a93e0824b674fa7171aa5e3415aa48360cd07dfd17b0380cfc18f627eace1d2b9b92ad7846171666bcceac1c2cf1b81e4264090d65e2ddbe2c048b39a37a2cecce3bd36fd1f40d084f27d6dc6045485be9992ccb4715ede65666b6d7413eaf383402b3ccd675f0d75613be9595992e26b01c349ca3b018df11a6aa06a422009d1ef505bf247a3e1e76926e8de5c93d2232e335978d8931e45fc72be3478741b920c19051bc8c076bef6d35a282a3b46df40ab55560041781142b8ee8c0c61c12122512a127e92d77a2955b16d113bf5320c8e1e2834d724bd5ed38557412f14d687f138285379fcfe419b5ae068150f860115b297bf8a50a4ffdee9c0d91016aae92923fe0189573fdc08e1ee1dbe7742afac158e7de1d41eb979e7b11f5ed1ca560d3ca4a71ca539d048c94f156f1d0dd9a88603ecd60d7c751e63c7b22e4e4bb5ef878ee110c8d18ef01d36d61659bd07ac353c35f4eb9d0efb0b7abcc0c1dabf8b460e8ed143b57d2c2fffdb58a0c337a5dcc2401496621f00cb548f760ac920a5e8e2fd435d72840d0875cbff933a4a356b4896db47dd3c5904ee03059d3e9e445320f7eb3d96328e00e0653b822e2eac25afa62096c5a3d28c52fb7e4e8a5a7e2c017bc3bf90ec4cea8b5295450c6c5f66d4c02714ff1116d1cbf8c1da3bc05c3d9a0fc42614b7672a006fb02047d4fa73b8ad8dd502dd162ab2edf10a6d953231fe7917f719ca0f4fa6eb5d854b5a14873eb900ab1b6f62fb5d2817d9c5073f1be900eb620ea46ca1248ca497f56e2321403ec15facaa76e5cce455826d1131cc33f467c999e71ab81d31327975c7b2a6e5713a51060e0f9a92e52f50be06eb2e6f768b51e8bddd00d739a43d3b4a918f781dcc7599d79887dc8e47c7bb62101ce9c5ed4f50b4b2ce501c97054a2e31e2dcdb811c1e4147dc3a38d5caaedd755501b45c2d126d3a10e5d2b373f3c2927a5a2ba438a506c0055bfac4ed40571fa68e08ce4da3233808ddf525029c92266f30de1ae3f63cb445591b9e5281b261b5b2a22bda69ad2c1a502d4d54fbe60ac8eb5b060468309b2bd602f0464894f87e384b04e2de46c4de03e21d965713d1e0b211a5559ee921aa582b8ffaaccde846e9896f1f9ca30efdfce600bfd53f7b0f7a6f757be0cc93fd14cf26c73a92c2bc427449c438ac1e500650a2bd6c2c0dda13925a1cf3fe3f9f3ba5466c8f421a54e5b195215a695c7099e07f746d49510eecffe5d87d2316d099a52b63d732839de11d4ffa90ab055b5da085aa0a966a3565659b5da57098ff006fa0465c1fb00bf382e714564ece06c512d6bc6f216e74d5a47410db394a85c13b014147ca80905bc2f06be6555b15b508c03e72503b75c96a00d4b7949f1183931520954eaf0ed1e1cf0b2cc70d99c4b67a0fde5e1967244192dc155c98aafb44d90ae6f3e0393fc14403a08f70a730aebac7784f35f91206e8cb73ff238d3587288911eda5a289d86543e4cd13f7baf87448f88b63cdc3b3496f0f676e4d997ae2aaf8038593b3509ca9ed06553605811fe99a04201db4236deb22a698a29c68f36ca82770d8d14a7917458dd067472759a6daaccbb9f1ad3aeddb588a165ba0a665b8a97eebe0b69fdde3fc2ad1853344016712df630aa074b9bd4e81d2d9e4b21bc8b9d62aef165fc6005e14f900e388fa7d7c2c8c7624f9c5ad769addeecb1cbd38e109b526c346ef046a6dd574769948e71a1ffcc7fb94ed3ac398ed1a7eb347160467dd095f2ba66da4306400b5e49cfa9f96ceaf2901da946f32fccd191442045b8111fe95d6dfc098bdbe38ab3ff756cd0393df1f57bdf87b665beb2b9f81b719fa4299726d911ac3eb14b22b3304d1650a1e340ea46d7b3296b6d8870d355f77f0654b04ceb23c9e0ef75cac20e430cc3f025134ee9c7244a43d44e38aa604f48e3011f98c33a32a99c61000db28c84d82ab3f9d4dbcf4dde02ec4a8ab6b837451e309db76da39702db804873cfa019b59f30cf8306920744ea87bd99f5ea08499d7ef15959bb592a13ff61f873a23757a46f7c635f9ac8304d7a8f0a9c8a8f8b5929fa4756e3dba981492f6ac19b61774fd4bc4a2405871492f6122faad0ba8e1b103591288387ab481a0ea3832d39de102760058c406acbd9b65df88cb2a3b0f5599c4524978479e880de612271b369cc66ed1ae99d80b132b8054ccbcf44eb09451c6313dc524700071bba7e048ac4314444946c2d74993102559eb3573713c6d63c42f65a8bb57dd873ea7e0bfc1e1287c45310ce042c162999c84fa00989beb2691054464d2fc5c96158b6ad1b59a998b71b8b5adef26ad588d6d5bf206962864c217bec2c2a95a65fbad5781ddec39af36c088ba19c7a01c6cd8efd59d34c43cd6aba668e44f7c6ae42dcf2c3b01b6b8cf25bb4e87bfed6462e66ca256b136aa84276bca9de0120a3145312543568282582e447872081219549813ce1095b08c7462aca0fac286437c981e8c8c5964941a22876046c5c3a7802cd8779da1ad84bdd8a31d81c006ce11ed5bfa289fa1ed56492e01247e0ea9334010f676586c5a6a9d533ad6b21a42856c3b74cbbae980b3444184f21ddf42dec291a28738dd8bfc925b9b6baa3972140d4b2df43f205fc52301f3c43187e1d01472b31ff422d9d580e996a3f2a9bc26345d7f47a9b62b1bd7d5e756d50baac3dd9f73c7293da33682c31b03ba972204ca04d0a11065084770601c300ab7f6c403ebf0dae73d9c69e7b64affdd2a1f0c287a423942da89cb98d3565970723f9d6f695a04b5eede32d2d585c4442a25f078428d4e7540af9304165adc2f547998ecd0b510ae49f34720808829782894c671fd8f24e8bdaccc8ef93b6be9b67b37c5955bfd050e1e65e42903d3560c3423fbe9ee23498611633cd7d281d29f67a0be8a036c84e4113bc150bb9872373f83499e0808140c5ce127a14cfb40226ec24ea0168c6abcd7409df4432fd9ea44cce797c2fb89e83e985f4b3a62e0914ae362e39b884dee808931a080413a4c3d8a055b7c9f4e461b5875c0ba507fc2e0eca8ad78e7ca550e510a2deb5c4b3e6d2d963ba08a49d8fe1b3ba07281ddc234f46e5050a6c09dadfeb1283a19f02c81a042f606ede252e0be0a49f33883d269782f5faad65a9cd1509dd6aea5321c2c1a2cf300a4da6ddafe0f50bb62411eab1e49800fa3494838b30578d6926911e64802ce3756dc5b0607008e858383fc02dc81444fee824e92571550a544f92bfa860dc7fbcf96f0ed0d7ba011e788ab6e0d1f7f839d3f2926af360d8fd9e276e7d834666601243fbb098d22042e8fbf3e0a02355a106705d5343db13be5f040ec3c892cc7e3b6667f904b32a485b7a6699bc97c2e6be8341243f0697c32df14fd060a1ae7d3f28cd129d985fdaaaf0ecb972385c9251d05697d4a95434f2e84c933c35d672001fd2066023b5040ed1fdedcca6801922b301f7b2f577eaad6b00c3b778a96ab85112b29ce2b6d37a919d263060b49cbe21523f8e546fa0bf517b84bbb6a976e2123d5544f94e99b0400f86c6670970896445d00b966494a7a58b62027d0172b8ce0760f3dffc30e1bb133fb017094b16ed978422929f97b604b5df52a721909d4b26ea737a870efa1f2ecee0fe994e8b5275d8d634085c5892c22912ac64be5237b40f00a09b13f5698dd6eece0d9f89cb5d70c08bc6775569a0c74a999640caacd892944dbcb3336e8941b01a1175f57bf27d0e872ddb94bc6699c7b8b9e603ccd401c11ce7e0f350c3d38bca00e3bf5e0842057615c1e879cac89f5a73f2d2faef41ca599d2387c56c913a73435b643c90624d2b744eef9314a3add7b85a61d8022b0a6ce8164cb3d481b847252ee820fe17fbdfbcbb9994852f844d8ee5615723c9393e4a596d858347afe0dfd9832e5f0a80ec8f6ca61bf6841bb60e7ca7cadb057bf41c8b8eb4d7fe16008ae05186f5e14a6075f4e6df9a1875f20de209cc33bdb85a48b00684922ad2107076981807f010abc063de72f97ad500348c0176807820bcc242702f0c22bc000e8c558629bd037005b5af8de32d34c864bafeb845a5bbb621bfc286f587500c55c10fe3ae4a4a878eba0b06528e97fd75ace677ac0c0815b6e7a2baf21d1457e3edd35ee17842e5766e24ed9d268c51b88614f71b5540442f717fd35a002d3abe784f2012c912196e16514548aba933c22eb130aba5ee2955c387856f2ca0dea65fd01e6d0cf1203bb6ec8637be758bb44d7b75a31b6d66ad7c020ee30f4415ca37e0e05197b2dd0f658576be0ec21646b3e13ac1d515e000877eb876a8f34f51c35e94c20b4da188e5e2570c890eaeca4835615b0386057b4fed12d501e211257da9545b68d1bc782612a14cc1b62af5e469c028b6dfed1299ccfa9c7f0467db05a74772d6addbfa430ad56793eaf7ac4e6a6cbba1c7e4d757ae8ae0a24c933b57d5e05cea15c682eddd60187ddc207ce588497c1d948000f84d206e1d1cf3504c4b9cd7a538f573b17a84db640e95765e385a7bb082cb93c92957cc68fe944cc6826d4da81aaeaebb68831af4347b4c859b3a4f6192937de8ab240d33494b735382ccfdc4c6fc11152e07970f98edf0c8394be5ea318cef0eec4f8142573643ad5fdeba63dd0fbf0340119567bad5f180998b633623630385abd1010c5e4c5767cd07c0debfa8a168a9fab9b1719427990737d0acaa5a56fbb11c28551d43e7b18069911ea55dbe525f75660ba16cd62ab645fcf4bd873fd3c4ad3c3981729a22ef371a7ed9dc5d78ea2cad42c00ee198d79195505a5fd58013dfdcbec3d395f9d400589cdc53ac38c1ce0fae2d5bf93a906d494b82846239afa937693e9cc6be2e35323e0e45d35c1bbb3dfaf12730baf0764cfb15209244ba567e92f161f8a3096eaaf1211460870b80a8fe2d5c1387689e696a0e040a0bb4245cac088676a7a7b963b34a4d146e768bd7e40a6e840ed651769dfe32f61e0a518c34fd3ae75e1223f2d207ac169c82d1c376e18ef8cd688a38e242167ee26f2aae89eec9180e321204eba027ec4fef032f65a527460119ef46b4b0743a6b3515870a46166222835558b0dded55c5c8a0f9319d5b469577162883e69bf0cc5e36df91adfebd6fcad2e36ee8bd7fe3c605e2fa18a14cec503749b8b045521c4e9b1649df291d2cd25c40a17a93a13ca5ae299e6923be454b36a9bfc3819b1952a9bec20c24509c6640b0924d35e91ec8b824feb84a57920f57469a2e4f42d959bf16d22919109a6827cb46a78ff64d27902103e78543639e3a7b0f0899f26dc8cd3e1480ad58a2953ee561733fe1d06d102ce8c90479bdce2d3224c1b689479b76d83fd33bfdfc36e39dc7214b50759b4886030348bfbca8b130b05355dbb3fd375d7f7729192a72db4bdc207e8a664ff753f8e2b7768b0ff67957bc14cff790a566361cadc3367fbdbd08dd1c5e785a3692c3bf24a4778290a8522302978970e5480c690cd3db2e75bac7a712eaaf9ccc6c61d05d9877198d4a4a33f52e44294b349c1351f3fda863243492a9f3e9dc187a1d42c9196ca9ffa15af12234c4d254d0678e4de910afdd4e547fe4103afe7c6750d6cf0a37093ffb3fc25dc01ee955d57cf3af69f02c0215df68b2d2c1d318125a735d803038385373eb742e50591fc800b9a799731e468c8654a7dbf43ce86684f7c64c8939d7f4b563f9cede26fbbdcc66aacdecf603ceddafd6381331fa0ecfaa1440d500aea27805ebcb4133a74965752edf2d63d031a45c07771566c249f30391cc0663bb433920a18aa0e7ea63890f39512b2147b8c98aff7990e8bb9d8a24848507121c52875705036404aabff60b65e4d65c22725bdcfd97091293eef8324698e3f261e6894712daa53c7d49d8fec450d7c41d43bbff7ef6e2567411ccb9701d8fcd004dcb8f039d5ff923a03e3dfdb003e1bda22a866c99cf4e280b16eb6c0eaa95ce738356130de58e414da7710a6ca03a90023ce9c1d359720265f2c647248d749448ad8ab758caf49f11f2bbf5b4edcf6ab5dc00d4c2fde080e45981ff874435ba35a9139e986179848698b481a38f97aea635e5da1a1b082f17fd68917187d39df9e920322ba535cd1d01603606fab67594fc4d3565636f0fe020cfe8266c3a05cb0b2e011f2568d933a027739c86232423a39559489aee0d2f660da9ed0ef2223b1a5d5ba0186d7a6b630b147e0c6d21a8d65cf0731f8e9a8bc328121f7a96a60660ff27eb7fba40de49318418921725f1c37b0b1208689ae4f7f1c97e9b844d2c2f5684efc8722352469621aff339946e0235c7b8c619619b4bf477e1f7e5af4751027160be6706e30a573e52cfca2db45e007f46e3603f862513dfc253cfed582ebf86c04783a8abce33dc66906c6ec789c13ed3f3e61a6403edafe6b439a990a94712a8da862ea18909309f1e15da8b8812d95fde3b4595f624589fc045d309f287b5a5f22d8c50df6b7c9d69355a9dc9528f1dca2be79b24ace35632d002c45a672f295a85926aa9abaf421f6c9dc02418e68da3dd6303f6dddaee5068f014720a9333690e967c52541577ed438df38f8840c54e6044c7dab9409e271a4e0971f41d769a6855690171940aa8e630919007ca7d1b531ba2a28e73c8711ba2a16ec960591b2735a1df2ec8a5c4e559efeb618119f1949763f8d3ad5d3c1a13c2cc76c65b1247550fc56a98004c10dc4aa26c71820d1a268b7f5f1a8c9c28648130b77a340b8408bae9ebe062d14594a7af3a2b66135c144c1958109833bb5c082b349a0f88153a60f496253607689dff9fb5d0751a3990cc9ee5369a09cf11e2e6ef4f2f4fbc485c10d2b599959946722296741ec315d893d2ba0a469086ed0b704802be95535b4be6784b3033665f6d8ac4708d8bebb1499f56343eb4c018fed659f153996de7c5608778acc0e6dcae102b5109e4af790ca528c11ad5f22563cdff474eb3d205a968c5ae89a1ab5185c1e9a83d9bd6518f64f5f5c231188835a8076a9848d6bc6a9c3239432a4b3dfcb83d109dd851e4966d5c11ce0ed56579080ad984349f5c21fbacf54a533029020a13abfcde1e3cd4c08cc4aad4b7f0e8b70f2d26d9cf0f45097ed96d749aa52ba5f269de075f58ba5323d48374bfc4a69aeae59e20f9da4e0fea007366e811d44f89c0ff941915304ec2da0fd40e087b0b010c5e7fb3a1b15869328831e09b146f928bf84ca0e723913e65180268fa183a01a967211f516f44cd23e78fbf11886f6bd31518cacad4ea292b9baae82157a7f07a1f9c921b0124d6a1b84e0f4e94340e22ebb9540445c2242c26f46e121c44ca739770c21f0d79bfc1f0a21b08ac1a62997329140b9ac0ee2e4f0abd1841097c0e478cbbcd7d87e3692e3bddda51c6fe46172bccb2b49c988f4cacc467761e9bda6d8841020798852d4cb487afa90a66044843633f42aeda7c4634748263a826f7e6c716bebbc0282bb9b0a2bb64586510255f5610812b553560e9bff9216065f69ff98b329bfb08260a446c0f0392df4a000097c626b9704fd374798b816790d1aa980a916b2a0f5c81b4e84b1439685481d19b3aa951affd2a10164082260d4abc85690ffd88a372a6864b50e43046d78ec4543cca8236690f8ffc224531822318319fc74f1b5283e4a06e1f597b0e7d2f88ceb3edc721516626e346f0467898830e92e4e65f5930538fcdf08fd902f379e5586b39c844e126d34f96fa766ee72be3022bfc7dc4fe19a18267be10bd41cdd968144dfe318b7df421e5b9e790cc743530fc33ccf10553a9135ee8940f3de66ebe6be97cd9580f3292cd007ca8adc456ac9227114e20210efaf0b0d75381f7a27ac2d1229235b91def09acb551059a44f015b1ace6bed269e75b861084ba3828fcd30e61a1aad05388251d48a61e64e14a5ff68eba8632dbdd149123b8987169fa3c50e95b1b8a1e6d4cd79e591f51f4b595b6ee7f38a0601caedf274b853c2783a1e2def7525a03752d44f82e6aceb53d7f158256ab5ad1d4fce2ad6283d4d4a5171df4a09730ac5c6d5ed026b86dcb5bd25f8829f652155d813146d7b4abfc744d11c225d14d2a31220d3978fd5ef2175f1e6aacd827117c312eb12319bf905c0741cce21d6e438f6af9fe1d9e7236d1b317677427500135084c6ab4905e7097c006553ec031101e762eceab3cf011359045ec35ea43693a8dddcbc72052f1356fe819b051be42ff1f1de48bd39cff5dff63e04898dfb6aeb5750d93a9c5573e1e292c707115754b38e758787066dafe481630eca4e256bbe328f802c2d050270a8c69a6c96e9a14386c8d7936053cb37649adbec1706ba86014770e8a45ebfcead2b85174729a8e9f7eb76b56debe6d39075c6ef37aaa4363e8bfd7bb764f7581de99500301b92f67c5920d396762ad99c8b3c623149741e176c7165cf137f84131f57c9c08360b39e386eaf939c1610ec72b1d514c26fcfe0d4963f37da06af3dc152f6d2890b4abbfb34dc3d651d6188856fc9a8113890b01f3deda67045168e1f1dcc01b5fba8fb95df7f7d6c75698647deb20252d6a6ae7dd6065234d826682abc64f8da11c2c73cda8d4ed89a8b86f916417dc94ba2f7bd2a7a8e9100c8411bd2197dfccf0a5603e6506ce762e817fad0430511e5a13967482a3ffbf92c60d826aac19ced544d22d1288d07d9ea7d460ff35aa2ea82a98e79a9e2bab0c28604167f1c1878fe4b4ecf3bc36956798e676767b78fb3b515a75a34a714377f983100764b61358ceb4152961443ca4d2d334abdbf553550eb92137924679c852f16815af77b2015ba0817b9a98ddf3ba1d11879dc87b09ad52c8eaf6719ee3e654c77e924eb1a8b8369e58545b46c5d8fb2f30be78f4aa6424230e7970b50e303819bc7762a96d9a79f354ff7172d117fd0acd6bfd41377f5dcaa4a4e0dd397d8765b159f65492070c3657527d8e7ac95cdde897d54f34eac746bccaaf12b41f0c3254ed1f11a3b8fc2fa55d3e0a82e84cff29b5f3ff828772d1e2d0009c65225fc98517051c4b655aad449951a6d09e41f305190bf952a5ca77a11053bd161b0ead1330ae6d7941a8e07667d5a9311dd65879dc7a2857d6afad4b359a66943fab6de1b2e5651512a0a20cffc7ebcce74d7f210582d414570c8dd5ed4671e0088280fbe57f599cf81d4fe7edaaa9bc68d83f8946afe4d72c5bf4bbe105e0e576ba343c0c46ed69c37b6249e5730d8601fabc689b939f6e9111e782143dd390bbc369ca31f33de4e6bbddeaf88fb45de23c181f3639a35d7687792605f888025561aa7022d7f75ac4f46151654d1d90f902cfeab15ba5e58195b0c5a73b24931962ef7d281dacccb2ddb9b51b93461c5e273f6cbb926b5ff32720c67b3146e9e38a173660b798edc4c982f3c5ef0cff9f9d26bfc40c436ec346f1f728cc50e3ca14da161ef0d187f4386a308ae7e0dddc5d844dd73df681dd80949beac709d5780a6048bac8a36a0f008b590677a2a6c6a1626d30b4d074872d316ebab519bb9ac070bae0a8b03d9acfd5a1a83192e8562fc099638fa5476ec4e0877dd1b82d59065e97389b324b9d25fcef07eb24a630c8e295412caea10b9be9dfe867d9ae8857486970f7ff2e57faf7fca378fbef8a9dffb11f7695a9abb392e810104e39a22c239411d1fd4a05f7b49fac2117aa46247348a90c21e11289090921db95ea5234ba3ea04a43efcae44efe77016d39fe165ccf12a05b32c9e266fae82c0488cfb8eb68b59da389fda6593c92104d7726ac7c922c42dd562d0173e4eeac669432c165077823f0a8f017f9a07a712b4722873538d5985170bd6bed00281060ccec2aa9b306bcacb79e19851954b7ac143aa05d18f46d696492c46ed831fd14263c114ed5c8c78f3170b1c9e0ec46083afa31a3976d4e1720892927f21e28bac3a562a87d8460d6dad353984ec8148a28e0e018c107e4ff305026f312cc49dd04b4cf07891da4abf302175c6f4da4d407573babfbd286bbd215ae9f249fc533016d5823f1f7ca170113180534d4a0a75df0026a4208055c7dff68365e640386f4a7259dc9b52b64012268ef86018d20a16cedcf7fc2147ea8a61c818b65d8be736f9a33cffb6f4298c3ba464825364a771307f0e2245e7801941803c53406022d16d3e18ba5ec9c3c5ede66789500616c13e36f9e5e7e6535b6bc3e0fb6128b9c06c14b1871b6d270071a4869ce23b4783766dc029a976dc5e05879f7302b8ce1ca4d856836b92b479ff7ceed694cac98cf6da1424eec724cc0ca96a758c1fd69a7367da0bb5773150f11948b13152b162ab30ced998809be15db5327d7609c217d8506c7a2a9d5c7ec6ad8006da39e3c306146c814a2da24e81369f58a603fc2c81e9f940b1ab89ed40d431dd59221b69e860347044a18660d9ee24a4dba4582bf79368d883b0f208026e6f5ba5317eb5db45e5472a7793f568655597509ce5bd2f0b371f39a77c737774ee53933d32f6375001b010ee97628612073c7d503fba3249c7c892130785493b5b9ea1b7a13817b281e978c289df8507d82bb2e44dc798f15a91e5563d5f28115d590f8ea5d5f368d212c8f9661cc5ee57b68cb01149db4798b0065b2a0aa02222c7d3fb0b880e82f5d3819b614721cf56b335219c5e0f5994c9d3d9c5912f721aee3b184e4732715f71a2cb23dad766c571241e53d55e0af52fb1916fd5fa699b418faff598d3619583413a1e32556525c6bfc403136650f6ef87ed2c1c4de256e14132fb3b1e498a0cef87e2357e5c7145ec605d7dad4fa9723d174f9cc76412b1fa269de98042469845b6b2105cb64370da82a18e668bc4b182ae79053dfc8e8533d68a2026221e9ddc285c826fb55763d06e107f4b65f61f8bd7a6ed68e22970280d866ecfb8d3e3c6ab6c21f7f2a46e71b4bd1ad10e49d26ea8d221065cd3d523c00f8c09a6053ddd3b9bfe3ce5365c7a36ee2320d2b892dc33f72abd27436f23bd165c822a080cbc4811de8af64cedb9b4e2cad96a6ce9e9f4fce5432142cde43944c477856635a842977984529b6d43e575499a98dd7959cd2ac0cce6fce23a671b2b154833937b6381549b21f655f3890aba2739ef4bb2859ac5534b46d46ceeca3f0c5e1c949d834edf2b2e4a4e9ccb1d252ae3a02cc434f46adffe0f5ebd407aac94d0828a3974e0e127881a3a30a1d928a31978c47c8d7531edf7993982d0940950681bfd77bec4099d082a8ee80b107f7309bbc81f9d6954f147db17724d909f38cd8a34c9864e978e58eb6067c2680e61ee5a59d09f23c4990053173d8e7aa40d45175db6fb9010054a34e5329040a05ad68acd335610c611f812f6e0ed2c7c897ddfcf12734eee3cc31fab48071c08c45be4cb2fe1b4ace30e65afd0fbe76369fd951e0c242ff7416e2cc6b46bbe59a4ea9551fb01af8d4bc0303ec031348b49f77fd36d1444055d4eee3a778c983629543f1fad81c1958be999b160ca7a62220bce9c233963120cd05e478c9b1deb3a250aca8535e4f8ae3874b2415f4f9526916bae0296a46fab037ddfccb730ac923ce85231d051c9f8f64a37e43937806b68097f30cd22bda4e8935336023b670c4a508a61c38676c747053109ae53579894a9e28b456a817ed45e2e2a0a2ef6b1455a4c720315daa5378116de065c3ea456353ea69443921ad20e54fba92200b540977bca44d26cb0444a6d0fab973c2ef8b98694a09b1c44e7cd047f3c4da7e7a03846177a3773680fc63bba43f21d34b1a44ce8551f15cefab13af9e95dffde6e36f6e8e84e49e2b403706de5fa86e68a8a0d38c9a8063833c4b94464026d6fa84a857e439b93cd0e6a1b74014ce914a854549e4ded46c29b1b9ef4d92da3e6470a4a0496efc925734f34407066b777528258797174e294ec2857b1647988ec61840976dbf4da9fa63196eaff0b9bdf2ac9446e3518518346e63a96fba3360d769be52b6b8d572982b36e7a25a431cea32ba86d99138450c65f4922b7014b23f13db201ae224c8f06aba50f9712031445ea7240b6181df22b6e6587c713bbca22f8dc394329548ca6b17030a623abdde8955551ebb23e0712430fdfcbd3ae92af2f81fa104374a60cb7c4d3c50c026dfd3fcce877200a53dc0180a229a62b2d25d7c0e8ffc9eaf60db6fa16dd10525b6b5e33b681976dd9413cef4d564ee4c04417b5ad53fefc850eecd3a8dacc9bce1d22f146740e8c1d7472d14bbba3658e25802a2e8376fa4419e4f4d62d0f97562d5fbddaa1d3ad7ee1939039ee1fe7b22a2d750476284ae4d3da2d2337e40c77a9ff4783749f14a3f450475906b164037a6bf4409c4a3997d6323cba787fc2328f1909ae3266864c7be592d23a214234e23fc02362a25bc195ebaca45983df7022da912b1ca747a0c9d29701b0714b0a8545b39c1891a9cf1f4b988e86daed65a69886f146e6a9cee33674ae41634e3e20d119639dc965eb81806d3d0d68c3d5bc4720da27290ea35d41a513c4a6e0ae074dabc0f397d497f219571cf9f15c10bac82f14a9c45fdb8ae35c533b5f853460e9658bf40e99a81b38fd8318375ee9b6e546f550a2006e98b775725dcf1a299f10ab32ba213e2fd6b69f6c510b63b5b9622dd0eaa07b12494effd62e6b2a4a593bbdee706b2b53bd16ba2a1196879fa78798767eabb4ceae0b9d52a6690d74448abcb50a1f43f950815473015975729b60a487b11c02203f4889eb6bc3a48643bccccb9840aea2ab0e1efd70aec4528fbd6f2e852164a944980565693845bc04a0de724c2ff4a7ee03bb081f991070c9fdb0d506e1084a231013e32d06e1198d32c8c8ec448d8b81b4b151304179033aa66aa6c83d447622e9208ee6bbcac8cceb1b20ae522457a7fa9179511059ea396342437d2feacc51c7c4ec199ccff69bc0c0434b6555d44f75eef2170dc9972d68d7939c0e637bfb41f11c5d2384cac426827d10b3b9e97450b7261075e011f881347e5114cb13997ecfc60084ee2168de1dcfea59c9897cfe2fc02c9e347ded97485fee27c2dba843e6f4ec57e337b49f677cf3d1e9d4c42848e4a4d179ad0c1bdf422ceb811dcff90284b81cd7784d424ffc7f6af10f8923e9990016315b3fb1f50b126c28a2142ac98742a4739bc032fa0858a37fba741f20be0cfd3b19465e178992e11c119d8d3f406566147fd2741d91bf68b97478a674e19943510f5d4c0b081bab4927e927819196b1043792a3c03499228f8140a1d6439c026a8638328783ca9fa18e512a0bbf1830e887c7afe55b1cd8be895e1224a018320a384bf5eb0253564f427e9b38f25a94000f6ce6b9c1cc389e6347ff1c2a94a697a6c343776303ba4c4ff43d923dd454d8f2a05b715e818241ce46060d1152ffa35c2f09eb38635417f97ff9d106a092418b959905995ad31d303f6699460c79fa854bd6e37a186dfe2f8c39cc727833275c23eae2f2aabb903d1488597f143c13e6603b2f7448cfc28746fe02d746988a80fce8e7cb9b51f23180183eccd1933c3e3586e832821b410f4a9521dafbb40cfa6cf078dc3e65c71075281744fb2e6b6e01c5bca1c8cd11ae6b41d76ab82233c6ae6caf190ffce5bf557eaad523c1758f10a6137322e3bf8921140ff1c44bc2f46d5fccf4cee39f4de3a854ad50d7f3897aade66daff0d8b15de0d592b64a889d4c1323c79d8d138d0b805463450ee135cef3a1aa188afd7e90af9a9066d8e3088b1f768097c10f1fb8013594dc4717484f8685d25bcca62e1fad73d67956d566657b2f2ad3309ba57ce65a06a7aa98d8eba7dab0fdf96c947a48161066268cbadb9e1f1912044a62e872e5b294bf35a9143cca01ea3f14c436ee15ac43bed1bc0d072b9c45da9f57c16f89b24851427102040059019a1a146054968f867c276276d668b3c752d54c221e01263f21044f5f7027ed28456364200785b0258c0e1dd9ce1c635a80078ae2f62fd0b0f1dc5de404df74bf12ce3ff1d10a8f9332b62a1017b6e8ef9e2cb49a1b8aeed420a1d3422c370b03de8feeac3f0f4ad9f5ea45875f95178fc6086c2be660178a3e23ea3e6ff10927f0776b88e50e3a72e1027a9471b6904f8867d345941c7bb4b1816e9573258158e4ba392ead97e3eec428f6fdddc6ed0d9ee62b15398e8def8960e6d0d5f0e2c175894f7905cc8b200ff4ca0147c1cf85201ffcc97df085db2e9565d89cbc69ab354190cd22b7713a5603dabb9b1447335eaffbcf645960c66c46086e266554294108c257171b91537e751da80ba560219314c3f55fc6d05198bc021c0b3d2b84f3042cf9ba3585484acf53b9b6853876fd4b6de9813b8bcc8156f68db06b665e011ca3590f47e18ef1ca5980b1cbe8b6ab7fbf1f57fc9041e46930dcfc28dc8bd342fc9af947876774d7d2ddcf304a829f4632108745209f32829dfc221b13e8b4719bba50b5058ca1964f2b56a2036fc298ed426fb048285c98c2d8cf6f8f36adc3dfc4c31c6512553cb6a9c58247fc7834a48f2e402ecac58def374108b92879b77521de26076be56dd2f30559eb40694271d68716e921b07b680eb0d3d280e32cf978e41f24ed78c0f7e98f58fed6a8e60bf5e15d227ae1592ae346ca659052021080ac39199a67eec84233ff310d0ae0e9a7848552df9e3fd46a7d3f72c999a8a6019c1f3da9f2d1679a97d16706df92d547062a9974f50d897f2446148acb54b4f3ba2a07da7bcdc68329462823330d7e06b392494394f91dd53735cd1de3643f6306c8de2af3cec869d97facd3578bef4aed2da6b9633c632bdf50c1a24b325147fe29140cc719eb2018941ea3ae7f7bf681745261b2c0a3a9cf63769e1922df6a50b7cd7b97de35a3db74d469313ce4fbb1d48419c2231fdc7ca108d454bc82b58388fe75cd2e41b43b9e1067a3e71b6a2684dfc333aad0d74242d3b41eeaf3def0545b43d3514bb9e373545b405298883cd333b50b5a5a22835d01c5416424cb49663e8bd176bb5c4705e1127348ae67a66fd8ced114cf86e73198b9a8926cbd59d88577f3a0f926e9c6accafe1920660d683686185b70954b85108435fb13d5724b7ffa677129293a4ff6cca71bcf23115424ed0ac267bc9dd3b940e82ab41d68279db2df74a4a5e22e99bccf2c2f464c1a611f0041192321490b9b3c0fd7ba79bc58de2b35cb417247aaace4f342ffae5dc237500a5ed3eb69e1e6ac4748dcb2e1139ebe5612c011b7dc4fa639a12d66c309ce7c194cf489a3d9e4deab569d9fd347ee47c785ccd71cbd1f7f27671834e3acf96d46c00a786632af2258ebf1339359ca428a1c8b5d9e3d65e26f9ef70e787a9a5109adba7f3aeb27c79bf906341c34f50255e8bd0154c3a9b8ad1190f17ccd8ba68bfcb432189b080534e5768254891d97c40e0b4cb3d125283c63c368833c08da8925c7b081e562b6ee4c805fbb3f58f718d4fe75a5d300c6335a88404527783c459e42e1e2bf3da19f5e1e4bb0d06c6ed826db31f7e99d5dcd052513da566f15037284204ac6a3557b5817a4c143c3d35ee92106973ce6ca00efc0c38b835660920869e04e85b4b326f86acefb8254dec262fc93d0beefe919783b83c2e9a0b12da2ca3caaeb860957b0405a5bb6b4d3356a6064275f863548995f1110bf23ecd25984e3568aea7572ba476599f27d60713131a9a0e70dc3a5a5ba5f56f42db739039947a7e398ddcf91a5c39985a4a02cdfacbaae106c126c790babb2b09922732ec4d47d5ccd5d566ff8642d32a8379f275c2ec25d8eb5e0f2ec0d80c4bf53bc3fdd41066a7f0438f375f84c299c0a051a85d1c050a1ed3bc6dde315f5eb20061a8558810d3213b8e004ca7f8332e322ebde4f7fbd7f2dd5107f3f0c597e231fa50c8236b23bc6e902d0e711978e13cf20371e9bbf72b14d6e6145fa08c076c741f80387b2179c0d6d8f8785d14eefdfa7ef41ce864fd898055411b7fb8bf7a67a5ca2a7b3c02c29e61d2556eebdc69d42622123b3ce78ca952a2deeab6776c230f7cff2cef89e91c791479073eda87c0dadc68fc8947a24c535aba9c6053ab1fbfdd503330176354eb3be1b7a7a3f2cbd007a7a8292d15c2e7760325449d64507b4f41ffcfa2de31d40d7d0eba1419407069001d548a6e73bb8bece7c88a508b2a1ae02cb1d4032a37e902fb56168e841550cbc0b9fef9ea3c7186c84bde4da69710e482aa3c157ae6a5b4f2708f38e51b91a55827f5c5b01262f84a42ecbc5d5a3060c1f9c457f4d46390151832642d0ddaf3bb06485f1b5c95dab7b54501d0ceb6bfac441d939f8b34a86698995ae137b70bdad4428fea98dad15339b26bff97f6e8827d4bd583ca67e0f3c1b3e1d69dbec2d2ed46860ee2c252981956753523d45d4ee9509270a501d874cb24eef76e1beccf63b6a0ba0472ccc9215782a33289c092fc1fa41ed34df0c5e580aa74e6c2b50bcec6d787ff2d074c15f9e82411fa70f5fe5308fbfea3b0f485051fc99a29d0caba6fb3744f680b6fb82f1b645db7b5641c0aa8b4f5e3c1bd926229c18c69439e40cbc67e77369279ebac52174ea6d903b11d20698a32aace5b20a8d0bddae4750b3cce959eb2e67290423c5a1a74858c60177e5e33565c18c616ccfc0219315da6d1882c4a56cb6172c60f8bb0f0c086661e34701504aed3d7d6a5a81e6156e482c3f74cbde1156da3283cc3270ef9202fdd6e438e1fe9f35c611163efa1c703247802c5de878bec456fb27f81f71e6a23c54412cc6ee053b94f75918cc5dea341abc15da895082d8ee38f5e0029d0ad074a8e7c0d3845b8f594724266679c3cecfdc5a613872f16ec4b691ca51143ca10004fe85404815f469e566f89a83fa59212526d7b9feb62b45c898419e78232067b2776262e3225895877b1f9e189160fcdfb4837abc0df56238bb7f55c41ae34df1816368b784aa6d38bd470121389198ad0dc884da9278c10740635ab62bb638a8a539859f3ec874b7a262e6e8cfbe142aecd25937a3dd802497662e197ae9747ebafc72624c9c530a16bfc933d4f0b5f2e3ba163f9f36356728181c8ab2138b955eb01c88bdd1322daedf4141f307e186d0f05983143c178b8687306354069caf1d937b6c8086c440523e5a12a03b01a944110b89161402b73feddc5e7004ff7ce492ab87c2d5d24f4bc163e201df4773e2ae695c392d5dcdce6dc57902d217bc542645ba85e284d26c746c6cbacaa4415b71626740346df3d3712a8f111069877727f720deee0361da5ceeb1c0ea6e8284a579298ba75d1b985207b2d1879b3ed083ecb4e8b0b08e5e74c14310fb7e2b4ff75069833b5619361eed5ab45031dc27bd7987e412dbb7b39917f7d87c0af904dd076db1f88091211dd0b62210ae287b101971f71019e5898a925142e303dba0cd7a669d42a266c0f29c88e82eab9591c2294001e86c92378007b643d416ee108dfeafa8b57d76e68a2746c853712efb65ac315f8086fc97a420e372d5a001638b64bd2b44b383f5f21999b74e5e8c831526c6f8a40a6628891973553534cfb4d18b6bf2f41fa009e8a2fbd24e2d1ee0eb0b9cc1bbded316ea369609afe61aae588b40f4e79db8325de89743f97199f2a0687e639be24c5787a4848104c1649975e6eb2feb00b03061d811d87f5dd0be4da9b1be0b656efb7e9b15fdd43407cd4aff560062a2631b6a61a6bd787f453ae31827a1597238a2e804fba7dcf0fda9939f8f9f631ef53a189ca3063188794261ab61cc8d6ba57a61672c1f45cdd755e4ac2a8a1c3e84ef04756eb3b3e84f44722d14db131a6e54fa87f3be1b5a6fc1d43c5792272e4fbc30550d27873eabaf6d3d6db052e14431908541df7d99fae59b320b8ac71781f31162f15baa6e539a0a1af52704b030143e070bb68085055038d3d9dd62ae5941a52eedb34cc846666605ac6aa0fb74ab860176bae5ac08f2262af9fd0fe7afb276797f9559df36ecbd6463a3cbe52ab7e245d68ba0716e6f843a03b6041a0ca2d9fba15867864ff4c187f89d16c4296a8d736833c44be46f2eaf3d82544ccd9f8e7ef4daad51074dcb189e41f495e4021d9cfae3a67ea1a147f185a6d23bce5df827b1a01527bb7e1073cb5be2df0bc579fb1b53407a80fbba9ee25e18f76c3435b7ff0c800f00ddab191d38a61cbeafc9d0cb540b0098bde69771d177d787bae3abda7f50ecbe55f7ffba9dbfcec6530e224f7bfb2fa7b0306ead5f47ce4818548740d2b4e6a2ad84ec0d70617d09cd20d6d6cd6ac061bcc379dd64182604fa15db5da82ac77c81c52698eeedf52fcec0ad171f4e9ce442326a2987cdf712182e6e6d31b02d5edb25650cd3e43c374b6b939403315d3cd25b74abed79a33fa26f0bd76571c0aacb2da87628a53062f8ae319654a3acd1cf9fca1a887200deaa987021d39f95d4755cdca992bef28244b7f68b2cefda30281ec6cd4ed6c4ad84874dfec2e74e23684dc70076b2f9400c628ad95c1530dc32f23165df90ae66b25403efad45a8aef83eb58b656e57082790a31f35b54539b6bc13e196d52da139ac7e3dc6f375812e550a78a8c3cc03b58deb4e0e1ead5e53e613d6c3e25d331f38786973115f03533d3248a724b79e04b5a7249416abdf5b02de58054fc4cf8251536acf7f685c08458815c15e80e87420f727da0237157dda937791ee37dbf7abf9f7cb5fdf3410f05756f722db6fd2f3a091cefea7051e32fd76f677952f28f76fbb2b237dcc84671f02539e115fc60c295c9959dc3e0b14f3f6513686060de18f5ae2b37917cf3ce27e273a78849e53e59450573dc6fe82606fe16d4416373e7fc8738353fa3af662f6ee42dcf578d225e61349fe66daed2494b58ef36d8f4805b8732022e512e88b55635e21e5c2ff4feb6459b98a8cffc98605179c5194b89480881c9c002aa0fe94c8cbc2861c6b904e1df9e2a34c8ac664736d0a50ad5b3f47e94fa9a0738f559c65db5d11b20e14c8abd86f39d936e125125e62c1f5a5774e46a31bad445428274e11cb807537c0048a46c6186469eee4d460fe35f4d14c14c75f5987a26742906070713f8aeaef5ccb781c72ae170816ac147b2697abc164fce88150f397ea42542e60fa89c5003334b20c3577ba4a0813d39b7e96b9043acac0bcaa690bb2fef9a53e0d0236f98c1af45a81e2933dbc99a75ab90e45d098d7d25c6655ac53e3745e2beba9cffd16a4fbec5d70a5cbe95eb25f9022d9ab7b57915f8116d9cd41f2d729c0d8c177bff53a4169c08cb4b8db9cbbb78b4235d982176a89c1c205867d2099f7153e82718c0151b8f3443602a5f0bebc4ec08a69879f2c45668f5e5fecc91bb71a5fd33432fb5d100a553e9988e0b7fd9693f187014e595b423015de2873094a0930602dc84c42faadd4a09c9a4650a3d26f6468ec4b41d8223eefc9e8c1eaac0367353aa4c4b8a467e86f4b38ce369f02c326e5158e0e11898de34f63252b9ee353bf66b822beaa3dad78ca170430a1565afe413369282e4a649ed3ad9497a52182823209470202faf29fe3e676c5b66d49d07e2460ec50f27625aadaf3a8d1f8579bd5f1fc4d9e27c43cac2e006ea0665457ca216a03c171c6e0a292c4975302d20049473b6d569417e699043d3c71f5730e2e2951e6dc07b7f46c2d3bcea66294aabf5f709e69d3e1ab1f8f482b0d1231a2380185e3016e7da768be4c4f1b69cf0f8d6df71e7b923499d16bf46151d42f77d7a387bdd45f436ddf39cc02674b48dcea76747833f4cf4b78dfaa06b19e80c37cc5852f641c396827e22a7768ba2631640c8ef629cae0ce84d97e80c3025bd0182aac95565ba7e2ec95c8176aaa68ad1082e45bccb0918afff364bfe3b2d59a45ebfd451b52dc8e5713d9f50ea4ef1ec2430e819cbd25029a54c6600ed69f56cf49c4ca2c6f773057b1b00a0f37f9b8b1fa0848aeb9888340d54b944ae3ceecdbea14064d6ae268ad8c084c6185e4fe1289d33b8b09b279240acdc09ba1e73f9d961e5c44c2dcc629a64991431ce0e2bd7485e4ec35799b4b040a0c1bcc1c1c3babd3feba05f3931e2dcddd26219c2f7f3001cee6965a402dc0c8f07c461ae10a82834e60fd453a130d3cf756ce9f5ba18107c80618b7c9b6bc370b86c4cabbaa58dfae9e829d212a7ad8a7cf66bb914ae004365c15c56cdc17e5a64878533674be6633ef7508cad1cbebf367da261bdffce20c04c032c0f769299bdd63c2a9dc653d6bf48d37de5d1602e0ab1cd43db37bfb6023ff53a996cb4a4fe3ea2825d006a16ffe079970e8c3f64bdbc8973deeef3bdffddba6ec09c3c2306c7d3246c1a6df7a80316628c3a361d8164791267efc8793c2f27dbe6adc2fbfc07703cdcc725574b405ed11c9cffb623d945225845fe464d36a918727588486a30217abbfbd83b2b94dcfda69135410096458bf788adff2048d2b277f947a9efc41c93af95b09e9c95f49d8c99f7c7b38f957e7b96d88dbcd87e7172bac6c40ac9f4dad99e661551ce052d32fe0ad644a472eef459e7d7f9d31b7f51467928a8201553434a9aefd919b62fa42e962eee610da75b4dd8db79a4d3a7cf3f5a659984a65b8eef70033b0bcea3f5ac842d8254f00b297015cd080f6644c32d85bfd3dc20eb1798640c4fe73b690f59a5685e5918c936c7d885ae7b05aff41e9fd8da150aa4ba2c57458fc8558cabbb9a5eb015d5287fe1e6d86255f1d6fab7f8207183e631bdad03edf8907b4821193a5bf22349185096ddf86df999af5e621477a8bb61d72515b06354a6a4e549f0d6f4ed6db6cac0c75697dab6eeb5b4277e61c71b6941eb2529a2d4217ed81d085e641c80d0ebab440b3195feaa00d8b7511d7f415976ae11f691dd0782d4a257c862bd04a96a2d7cd158678ffbf11c44474e5aa14909eb8e21fd4fa8020961fb328556e3d1dfe02e32e5741f3c6091bee36276b88382f1561892c5a84ced8a0da45a24c09a5e1e425a7faba8759d8cc12af1931ec4befc38bbb68457e4e925bf1c1128bd09707e2b3ea1bd6f820002dc7091a3a74d68a4744c071ed4982d830f28723180b335dc21ef6a8873dbeb0d1d7c33a9fc025e2af4e721b0d83d13443afe14a59f76aa7eb5c6eb7a7c84d51dbfe38e10f1bd273603f068ce7427f4ec8c786f518e8cf01e7b9b09f13feb1213d06f69ed73a1b00789eeacfb102ab97301e39acf1aa4d62070634ae320c55d65e6d9ad5b7adead8ab0f4b9535579d66f56daa36e6eac3526ded55a75d651b558f75d5b0a85ef3ea69acda565ef57e76d6fe8fa03522ae9768d6f300ddfb3591fdff8379cce2f24b1f2cf116ffd790db9db9c0b6d223796081cfefece4e725cc7b4303c11e7d3fa139193372afbed43e6270ec7d80867c3a0742a76fac56f640e9dc18ac592a154c91cc6a36438e1c28d64cd15680c53b21adecbc59611672dd850fbed1eb95ff63a3e48785b711f0c5438418ee210a79f30f0b790965488212ffb98375fe153a5abe65e5c5a1c65df55ddcaaa87c79db67a8e5e136614c10d1e284dd149e08690c98e9e262b9a28893de092511b4a1e00319d837145c927828ac0a86b7953164ab5c55a905e984197a959ad70dec4857199689dffbdfbeae914ac5277af374be22cd896479d0e786caa1b206fdd813f0ee98f50a5b70d4252d568f8f4ef7581507958ad0ae861348d5bad258f836f3fcaa870b8b2cbef5685abc428f541cb0329d983227994feb1cd2787833411f5288c7a4f159227eae82ddee1bcb3041aeccf36e1c3e2bd0c9c05952eed317f51b27e686fe53e1573412a74c3e79f4bc40a3eb961f43071da8a8956faec00333f939d8400d75c1388d96d24e4b792d112b51e2e8ef0a1dfc10ffbe80b3dcd48e18fe69bc7c2b9bb2d84090ad03f060cd47aef2d75999f20fbc9c5e0038873231f691e8300f0d937283863c8c33dcc29743d0c9da34d40ae982781b3bb8d57bbb1e0875e51c325fbb428dc7b524ae4f28dc4ae25e6f87eb1dbe2d2e6ce6e639ba307ca531367ce6f7a290322b9cd33f7bbd5b186f916cd031b93fc49d217c23153148d081c83034c00322c93dba8aa2edfed4ce1a26c904c4df3b6a2af27e450bb9963c9755265648f1504355a8a9bfcaab2e2e1ee130e83669068f8193bc030177a3cdc505347e2f85331cb99f76a52a34c9bb67891b0fb2668a18de4f8ad00404f214c0befe54004e5d05b794e7e8906cc7bbb53dab71a6fa7d293ac5c51340b8a1bcbbd052d31491cd2be2579570ad251a1e4b927d39dca3a75b8c40555459e91c1146902ff1e840c723163436a179e34efe24ca188cd9529c8c95072c9037c13f00a0354c898e260411db115e6e0cbe1a1c6011e5a453deb2fe30ae5c5140937f0bed70bf0621ae14179055f22554eeae31e2d30139b7be480c897c36c43de0d6cdbf4db30428cb77656f06652094927ec24442460d41ff59a50a74192215a19cc0cdba2588bda407a43e49ab11bf49ac799d2c5db24dfc8547a1f46f4aa1dca84ec92e171df0d2a432e1ebfb935e8b5c2089cb717f1f27ae96750d4318e0756a8dcda69b5416e00e0b3a58700c17abf892f89e42798b83aee19344269f1c505e01c97049cb0d1420872d3afdb9fed72de9a0eca0df195453b31f2bb6ae8fa6926a4948af57d7ab0c838598836be1546c8ea14aab616d14d5ae6405797db1a98d683eaeff729becbb02cbe43bc0cb44ee7495c77cb93a0ce7aa63d251309fdca6f17ac1433d0bad2d1df5f5a5f63725ece921bf83bf6d1d88fafdb27debbf4cf351a38cd484546dc13494c4de5bc64cd51f8270679e89de8e5bdc359e03a4c107c2746c72cb511abed9ff3c7c89dd7c32423085991cc55b07adcc01d7908052748a0ac817313b35dd3e9594a4ec6970c490160e58d2a20638a5007793b1428cbb7fb0b3902d0edaf6c0ddcef58bb66701742ed555e771a73b458ca6cd211cc0d24ea6fd18e4013c4b155fc095aa8bc04ae282af5a4995a660ade8a0b125b663d85b80917ee16ce6a61b7d9c3f73779dbd9e151703946a03e79cbb5fc4b9328094fd755bf9a0e5968ecb6c38bfa4b97d7c2d6e8ebca409fc64b1dbd2197cb14a67baabd1363a12153b39bc0ba7adfe4078055c17fbca495080245b3a8d2e7e4b72806e750d46cfa0bc3116fdd5a32d326be1b4f82c212e1e7ad9390d221d68a61dfc39df3e3ca4eef4b835c47d2b9e3cb346bf2771778b869eb38bfa1b0cfe30a6541ae4361d8371230ab0c438e42fe903165a47ea98dc9049c737621d724fdf4ad0067a43cf8aedb906991d21dea3ccbb5c424f23ee5dddf49ce5cd8e56a666bfd19ef5cd7dce470640265fcc937d78d65f72c23bf8cd0d869c24418359455a488d36ba9c4f18757c72e2e0abc2f97bf5327b71ed42bfa2da8c8243ee825b332cefac021bd35dade4c3e2ae95281abd16758d899126ddb227516f9e44cc9082d57ede8463b1b356aabdf94fd2237517ee7fed14f20215e02ea38ba2d7b4dc1d90972ba234112bf39ea5c906ae7ef178df4a199bb94395a2fe1fbd18d5233675a279c95164bb606dd32896925587d8c2cb5aafadb11a281a302e25465b8fdb585b17cb85979a7a8af564587b7943547893c36f966133686fdfe9bd548d9bd7a7c1d0f7ab3befa4930d06ee0cdaa5cbf6f4917c7efd06ab8419aa5fb6a32a5c914934cf6fdb89c32f53a82dcb813015502f85611781cc2621d0225e486d01cce4a267ab7438141d1beaf5105587a0226be8c6bf4c447b3d9094cc9db1ed49d91a0a51a9a08284fc3068e8395c70aab4a777a1751cfffb998867cb745c7dc69ecef6ad3ef0896bca87302d522229491e45043ee08cfcaade8a8750382533d1a16a7712ff07b73ab3166fc787bc351879d0780156daf42a805a622d61fa9859fa8d10afc9d54cb014a17a587909586f3528ee1ec2c92e509612ef98f0e2309f58bbaccedaf702b443cbd2895d7e6657d3b7bd6601da15aa98e5f0055a2ccccadfbd12f8c211a61e20764986aad4606dca5811b00e83286f068a2aea0536df800e1a7cf0a5af95da8f73d8d89a918a86b5df5b39ab798768eb97d9cf3ca40f48584ca848130b0af1332bc7df163f75494672a2ebda5f364b7f44594c64b331d7d7604c861302d0d94b59dac11f372ba7e456aa83d9d2bec8c2a22e0dc2dc689d55d30c2ea7bbd4249f53cfe135eee78462de2c83e389e8e87da13b13d58537307d2d434af4f0bb75b120c8879b548a3bf62819c1214881c697232c2d121bc5e9029efad98ff1bb6423fdc8593faad54768fa49d75052c8cd06e4944038efa198392c6c2d6486422cdbefa0a662a35f0a1aadb681c207166af67ddba68649d59e8daa0c588a02a619f5fe03ad85527db56065232f96be8d16f2e371a15b4c253a74cffdeef4e8558ec44567d6856269e17da1ea895947e9a17bb61cb586ace373e4dedbcf8e8d4e1417dd1d463acd755c38708b0a77f0cce86cbac362ea0acbcb8f8db188a55cfb0ed64ff60fb95797efab79957f421803b648b00ca734e57703c6a5ae62c18e54707ba0bbdd34c72cc3d56ad3e186a508973f616cd2927879f6999aa5d7d4ffb97ee0c233b2c558cec9cee6d6fbecdd0588a12034d46502fe28a76305d4a63ebe31659f6aa725787033e08a8cd900d1f9132e7fa8b0fef381ef26668ad80a71448a1d74c9d4637d35bbeb3d56f582a7231d928b78189091cfddba90ec378d5044c581f82a20783817b88f0693e71260efa270f5577bf48fb1a4cd34f5998d98b19a5838c430766ef969a127b46334148de499cde245f19d73eabf3b424e9f09e7964d0e526ec7275510c51931c10ecf0a7c7cfb5110d27060d3c3d3ffdc01a62b7756e8737481152cd2337debab5c01df519da22e0e305dfb9f28a531d6dd22d2b6e894f662fc60a8ddaa6ec59b9ead501ad39346b9c13ff2870129ae59bbe1c5d0304a813ab60a3f855f291a042e7a00f86ab56c66422755aa34077aefd338b55bdc2eb8056ce03da00566a78ce23f15703d41b988b00edb34638734fe3ba915ee97672be46da3a389134fac5c351dd88c7eab563a86e5794be90ea1005171717429d159508e4717cc98853f504f1976d0d49050bbbfcdb3413e0e459dd6f42b74b04555fa3971335a8660032c7f6ac5ba29a6c04497025063a5b3f2430c7bf6d10c15c308cc037dd177f1ca4dbc3334e067920f078942885dad8136815eee93ee01451b3638a79db42a2169e90c0144a503ba6faa7c5a30141a37079d9c1aac1bf75499a599a3bd1333d9a4dc6d957636a2dddc6b0d63ee66ca4679f5e5bc41c866ef25645c4fec0bd6dc6800c7df6573b0784e66bd9dffa15b7e2ed6b45e44b2c446bdc728fe96365ada24b4899429a5aa07a907b20731de3396db7d99b590d8ed9bc69c26a8f6cc53c7bcc77bbca746d4f1036804cbfd0b4eb6c27096bf163cd7b1b8f843a5559565436ff5c0329f2cebaeb065d6ca5a592b75b356d6ea629f1b60a7deb8fe9e368475ffcb538e88e2329fdadd9d9da18004cb4ef6d6c46b21dc757313d6c05e9ef12f6996856ebeb065afc458cdd0034198e3462331c2574822d1605a17b55acb3e24fbd80efb9b9cb3dfd4029f5a0a2aa2bcda27870524a89c220a40695f6400a8eccb30a77f8e60c40a50394536190c01a58d4e8a645dfca0b2d149cc7c0ee9a8e560cb7a9dd4746cf35829b414db2ca0a9e82c5a082a803247ed88edb164327cc230ac62b95bd6b8be1bd89602c553d90e82a5709d6d7278c63fc9131538b93a5c7f250da5a1384dffa9775a49145bb68f92f6f1f1b9e2d33ede8fcb85d06dae892d4d26cf9dd26e10860e0b5b6badb5c27eaa5c7f6f4915f9c49f3ff30c17bdc7f008b664d70b318461183e69c31486190f9ecf38317d8d6db66fb07bb69c87fc4d4aebcdb6ab6977cb6ea5979b757afd3e2ea47dcd3e4dac9a266edbf669a2563531e3b84f1369a789d3f33e4df4af418ed3402ea4bdf6739b4e67a8b932e15524b3561bf41ee441d383466fdf8e39f647f48da0caf926d35ca5e6181281dda423d8f9620718f6de248207d56a6d18fe6b4f722d49d8c9def6cdb164588cc8e58da03cc8825b8b6d188aebef416c33ba2e12ddcf7fb8e4ff719c0ffb2d634e8b8fd17b4a74d52cc5f8207dcb93464fc5f4b73ce95dc4181f2feff22fa3abc6987ed2e83cad598ae9910647b9bccbe82da3ef60b1a5fffc7094dee92873a4bd151985435f0acef7441cfa96bde33d5ec575865d6ae9cf13b76c495eb7e2884b3b2cbef4a0ee0a5b7ad0f50f5961370faa1ee441ae1909b9680a0ce32e4e266fce9ecc9d83e31132cd1375aaa1c07a610dffeef628e3b2a5e97a6832997e1080982d1b093bd9b55a1b86ff640f8c5872eb3c91e43f4e9d2747f99b4c24aa6f8f259d637fc84a48c5bf37f3c4367c47c03ec7213ce35fc5b041d7df3b21d8b25eaf5ec37b33579b0b0bdbcf5858224e7c70cf4fa4fb7e22dcbf2c1127f389f4774f849febefde477ff773f4c1fdddebc0cffd7dfa227fb8f7e7ba691947e79c1f0d8cd2e9e7cf5a7307dd7af282c168ca11528a20a3882c538c5072798a11474e97a714a17379b290596ef38471fb656cdc2e4763988c1491830b809efcf0451358644f04ed2066042fa45081d7f5d7f43ff901c1967f7f66a3056520ddf9b57263997df69a902ccb5e4876671965947183b0a590ec4e127569414c21220a9e5b1ab9730a11aa4b5e9e4283d4f5c0a7ce5f6659f6b4e73ac8636ce27aeebade7ce27a544aa9ddac0451bed0e27bfe8c6dbce751bb990cd9fd4240b588dd0f88eef79ef641b91e371196a710c1da7c76b4ebbaacebb4aeebbaebbaaeab3442a59125d77b16790a1139b89de8d5923ffbd0975ce8b3a7cfbf81107fc97d22b0dc1efcd087c0186e22102af9b9f7d92235e33d8b65df9a6559c6bf818c72bf01eee773ef4f4108703fb99ff5bbeb5f4e1018a94c8d78ad6d01032ba00401e5226b910294d7af32a85cf141ac092ab030620951a05c64580ea84e81932b8ea05c6401098a882e584340b9c84160c23f00630a2b5e2817bd9613503909b0a204305411b0065074c1990055c4250accb1f0c5155817414ba2479079ea2c30488207657a82eaef187fbe62901f2f6b90213de6c93610ac5f55a3b2d7d9c10554f6aa46695fda981ed9c8d787a3b43186c798ec6bd07e1b8938f15143f6db673db6bf71ee20e29059ead7c48fc7fe187b38d9a679cabe633dc64c39ce1c3b32d9dad698f06c9f9366da568fe0b88e2733333328e20f71b6e219511019a1ed99a396cf65babbbc7c4e22bdbcf44b167221895e605e606060482e2d3030230b030323820981303030a6ef081818b549d90be8d840d43a3ac9b4df1060e7e74db5cfb2186d9421bbda6be39091ce39c5f058c6e9f63b3c6a1b9d506d72f6f46f0865c86ef6d938c4493a7e9b17b69b099e4743566cc6aeaff1950361e8bccf8644d5855d5a5a5cdc65489614926a584aaa2f4cc8902c2924d5b0a1157186621c304570bfeff648e90c1afc62a2460d7007c7732c71b03b8ecb658d0e00046005772bffa228ae100000d48811451a33421544981792288a2e62cb48b4a2288adb8d503f4c10301542fda60e0aee4dbd7e2f31f0d869fae4d12dabb53bbac3a33e5a86d73f2ad6eee77b9ab83d48df031dc0c2adcfd973200cd97b1e2883e952eefb6655fb8e538159a0ef8930cc02fd4e24f50dfa2dda02e4606bfdb8bfb1d2281e33151f9e253a299816507a5849b2c47ea834e151e96081b9a028610565b9f2734d36c2c892f4370c3f549af05015d5c102734109ca72e5a7d5c48fb80a088b14572b212388af849ab08d23d484a53c43fe6c49b0d47dc451da92904d3ae90c997886df6b8da76ffdf48672e0895d77e09329ed9fb77bce1962e2d567999bbdf7b494b9b5fc5b431bf692e600d23766a8095bfbb217da81adef75fb57e7a4d99d978e65ce659ea9d5e7e7dae9b3c6d03964fac60c8de09429edd7f92ff48df933942972ed7803cf88e6bfd0fd7c8e9bdd97fe897eed1796bee823e3d055e66affc5c860aff6f66a74d478e079b67b2ce9ac3c8363a66e69a2226dcba856e9b773f9371209766bfd2a4f8d1a402187b8b0fef9cb1385bcb01ee86ec30fd7696041e38aa661c5695041411a55501a5664f5d3401a57341a3ab065ad5078847868da130641d118131a998cfe1b63c0716bd2334fa3b185e0528bb5249801a3f7e726d7ff13ab0ecff868ac3a70c95f0549ae33b9fedb58c1d6f5076b60cb4ed59d79aaa9593ac2340d0493e00998a6590eaac016b89aa7518ae7ba3796e093eb60cf0559d77f648148f1d7752f1f0c0f56e3c6badde0a4bfb644db71d2e7a8f138e94f6baaa6fcd44baa2a49dda94d564faec36ef9654328e196953584eb9632b7f25cf7da73fde964398fd3388d97ef37b80f315f9873cef18629654a3fdcba1bc7496ff5ddd1aded38cd12b6692534609a0e82497fd776b6160176903ad2430b92b0cddc6cf0021f569d5167ff66ad36744a07ce11827a4fc039b656df50b1cdccc672b3010fd7fb5bc94eb35a9da36530c4660312b535f5b5c014db4c5005aa463da08ab278c66b84cdd258990f8e934286f8973575fd6b6a9e6a543e2bd6ada954cb2ae4364cf0b232461b943603b14db6c3013edd3013ae9341ce53894bfe3555ea2c79e85231745fb0d8925d2f5bd8b232d0c5c2960c04040808080808482aebc2ab3cd6016c03639b1648cd890ab365e6c4f5274b7cf2deb7b164e1fa6b238f954bfdee97244992244918100d69f5b79189e405bbb0da370ff266e36b61b5f24328b0f3fb6f9cebf80fc4d9872fe3343463923dcd9e34956c8462f9d7ce609aea248dc7a4ff2686fe3222934e83a3b4d1ce12aa4ab1760ad114a2299c64dd32337f562c8f22d10ed6da2eec17c26249eeaec2120478ccc528016042d52f57b84c6730033cf1fb4e6cd4a42213efaba8aa28ef452f2c8f369ca4945d3079c9399151212feccc28cd28fd8981679c131b55b9dfc184aadf456105f8f08c27a0ec299759289b07d73f014cc37ce253ff48c1a40ba88f7b23288f137b25366b76cf7d37f62aa69b15d3df8df5ebd83c63b76298935d935d935d935df3cbbec2960d639bee877148f27335710efafdd65f065ce217c628824b7c11d03798d422b8e4ef210fcff8c3fc63f8af551bc311068b7d3f54697087a6c39cf3e7e9fbe8ffffffd31fcaf8fff7fa3eb0dcad3e7687e8c630a309b67c8e39410441c0f7f9186af569f6f18921e59d01e13d1c4257c8fc6c903cd39f652de220a4823b42f7035d864f08ea9de30c3c799640c0226012709017135ce798d3799b7bced07c2a6a16d75d0831f08c3fc782c4623113cfe090e91c25bed1cf527418e0762cc8ceb6e3eeee5ebd6358ec0b37947ed862c4ec08ddf9eca7ef79e61b6f706f0c230c70085b86b77beeab2b8c76d9806301f1472a35436d4c919776a77e6ec794f8d1aafcffff9b4c24c902125419e670155be400085544e3620928facfafee6920b659c0f55ed9fa965c3d7105f87bee5eb97f9e7eccf99aaff99a2f7e0d619bf9fec4c91e1f01e7e824f8864b098e82b6c38f2043aad8ef961c04430c0dd9d3af211b6bb0ad76f9f8f7ca7fd542f8b265b7c856abd56ab5baf5e3dee1a0b62a1293c562b1582c56b358ff930536cf9cac56ef78e8fbc6927b4dcb3e0a665f4118b2394947b0f46fe62c8decdc0dcce79e8a25c3eef7a5919dcb11117d4e8c8f51072328f0fdc3197cf222b8f3150f564e912941200125b2d77ffb611b866234e6d8d1df3828c6f800dfe55d8ca03c2b76508c8f960e9a2577e119fd68f4951d9de5bd6061bd10c1961e731ff7f1d84b15b6741f9febcfeaa016ca1660f8c6728244640045092191512c506454f81d16b69c574c16abb63c0793b81038b3e46f3291e43fec4b86897f71867409f8d445e0923f126e9614ce84c160305863a971cbc6a2691af91e15fb950bab721da7099aa71eb34aa172458b56f29dacf708c3fbd34a5a89efd430ba73f574bb8603eb7f33bd3d879818ec5c6596a22e425c582b9674b2c5509db38adc45b7acb754036c82259dec1d78ac13e3e9739417022e4f89e2c37d02c5de16fb4e07f80b86c5499f3dd7759c74d5f539291deb9d3a3d8f9011e41e86bbae4bb93ea35ccf71c96979d17b45b0255dc5f48fde084a24c6ccb76f0445c988f908fde8a7e843f4966671d26bcee843df22da9c1c582afe480c99b0a5b358568a97fc6bb5360c3328604e2847f4a3508aed7eba57e7ea7c32203fb5fc774b1a03822d69ac95f9f8e9eb341f6c99b5b29e0da873358cc62a3886df58fe77598b1b4bd3cc6a7c604b2a856da6d028fad4b966c99ffec0fb925ed17d49b9a046985fd2245c20055c9ee2e2e2662db6d1ae7f9604db68efef699d6b9e68b5218d75ae4bde8eb3273c7383d83c6538e892bf675cb0ae58fd401545d6444ccc96540a95e2343ece00d5a35a46f6d293ace5a47f925b3a14ad0596fecf7f0d8461e41ecd4933d1ef038c7045091a2f4f714189b56e3ce6c796dee3b99ab8963069e9cbbd0d07c4362db00d6df9f9d965288a10e27a2d226da13197c7df092c4f71c2078aebcf77b6cf902a6b39e166ad79a231fa04ebf29428539a785def99a7ac35e5092c539ed082092f1e5069dd2996300fa8f05cffec4906e5fad71b121bd5a2df39c305b67a7737d7dd3e335cd06f0158e5e85b12d7a75c9ddee3ae9e96eb6766509c663ed348f1e13902b131e9dfb8f75590c7d318dbcc9fff52862db3d43d128b42a1601b3ad23178c6798097c6aad8d27be81036e634d90f519694c0ca2d6996eb1f4e61cbac15a3e1e61c59915cc2662eefc97ab21614d655b7cc5ad90da8d8d4afbba338a885ec3a0b7cb25cf21771a2eb2c2a4d220eea2141414141414356b40a9b6c3033576631366dd398bc8ec251abb547666d9ab1bcf96da2f695823bc8ab8d96c4699f06ee083d38963517fc4d0c1aa3405fdc50d302d18fecc8be68649aa79691bdc4b930a0e5e432dad08285b1d830862df9f563ecd6dce564bfa6698e849e7eaf9d749a17bb654318b6ac644d3b79bf1fed9c3dd670b29ec0765fd65bb20bfcc41a937d3796360361e0464a5e6feee06ab5fe55e1b14c29aa2c4262d3b29d79dac6d0b5b17687303c58ef96610c77c1906db21d26b6e3f1ce49129c83d4375a6c436194e89ab0cd7c52e710f9860be0fa97309e653bdc4fb7c3137a851d0febab2185d932dbb9e4cececececece8ea85585688f27f364ce7e76d7d1038ac852adb53689867517dd32311b4d0216caebcacfb26c766aa7837a49bbbab395d33815204ed31f7aff21f3f4bdf79fd8bdd775cf898c0299789f79ef39f78fdbff8d9d9a25d52c1920b4028be3a403b13aa6565a06cc5dca921603359525ae5b3614b2d2a64b6c7052b483dd5153a36535e4e660113ab68d0b2f2ca595ab349fb2675b435ed8d9bdc4a585cb75931d992693c96432994cb3566bc3903479405ae59ec57558739aa609c80464b22691c994e32c98e2041254110ec9112811903da144a39398ec996deaf5df78d8660157545f243615d504b96dba66cb96355657eba16375f48ab0a5c75af5b5d0bbd6ad81ccaed1938fc1257fc275a723a7023b369598077dc85b3fb6c67ab223b98da5a93479ec04b6742c589ca6ec18908d309c663ec902e914dbf0e53c86a3c6e4a4af566cd331efc17f15f355cc574042a368ace167c983b0292a4ed6d7365ad671c7fc8c6674344d95c73c88052ef9d728312962269e7999a82cf198c7ea0af496215a4878df9c81674c24a55eefac64e008304dfd2a537daa8f0f57b94a67680301d3642408e225e9a20571b2fb8797806033986cc8740e08902f5421bdc5b15a1f36125c84ad1b906abb1e9b244bf2e6851848b25b9b6b2e77ab63fb19db6f5f026073bd54b1e54f72dac832b16462624bd2a4a1ff9f4dfaf384f779dfd951546bc85a300c59ad3db195393be3c66bf70dee81ccd3e5923f57ed04dab6ea373ce3cf72b1582c1657371d374e368bc57de16962741c8967ba91e425ff26bdbcd4a7d6e285737416ac02db741078c66d98b66df3486ce39486506cbd2549fb9d256c13ea2cf8d42d2eadd8c6a1f889fb2aaf700ceb852bf117dbb4dc530c5cf20f9fb837f12555c71ef124ad049faa0ea0e8b2586e05966729c8f71228d647eedee0241027bdfbfdf224ae013e75165cf2976232954aa2f8027a01f9caab413a822dbb45ca38e3be629f1802cff56f2db0748eed5b6c2d78bc94b02599dd9244ba2114dbdc37ea6b608c270079bd5eafd76b76f7eba5c542477855aed76a6d1872f8ffaaaf202658e699155dad66ac6ae6c9e45c735d3535975f3ffaa7f2ec08c12ed79daef0ce39524ae7970cab4eb0432ac7a71170c99fe346a321b5776265d46aedff1776392d8413ad1fa08a7043f800d57d0e0b4850456850dde8a4ec57c332180cc6fd42f0a261482c8cb9635f10b661ae613ce32f433eacd5629b5e750f5cf20fc3d188d3b8da2037eee8ec5b487663b2f16b62ffc6fc3b0806e4ed8098b0060cd630186c4b85506032916418fef0d6010a123da8229b9420504e9eef9c3d6cb331c1a71ac54ac1548c804fcd44942e81aa97e8257ef8bd310ee8184bd2746c696b845a0eb60cefcac1ef9e9e9e9e9e9e9eeef9e196c0ccfcd99cd99c324c273e5071996ccf6005ec36a69ccc2fe224a65a61a71151485645266a824d71cb031c806dfa3f0a4ea8828721ba584d81722b331882142cc8a2478707943fd7865579534ba9e392d3885c8a1ef43dfc7ff8dfc4e0ca329c9ad512b0eb4488199d94d2e7e197f21cd9b3e1a79a39e2e81b97cbe572dd48d183d591cb3d40422501977b8084756bb5a19b4ca2327edaf5c978cf29d3d3dee69f97ff663215c76f8ae46dd73cb1af9c5c72fd7b2ced6d15cf20615ff3bbc1878441352f30b0ec426e08c2d2f71bf875691028b8a7b8dea2819d545ae5e2c1dd6eb16cdf925f9dba4cea8f97ab5d292f881b6fb2d137841999cd916783cdce4708c3728fc72b244cde1edb354bfe2e977705c8742b3664c5d2efcaf5675850898a0d0244cb461f16a8f5a63a95621b325e681c3f71d9f748f11390590ac34fddc44b2fd2c481e30299960613db414c930126a3300d8e936c03e434f4b9a0bf61d8208e33c0341f93fed958b9cefbc090c9025ce7691f08ce100a6f8f373e7565a968694162475b8b67ecb8b5b656b7cf4929a57466d9ccb21108a574b4a19fb3cc9015ab51fffa9e4f181eec16b262391ea2ff40226c8a95da627fd27c7c2a6b9eb4d72cf1380d578ff84903aa3a308d16e4a46b5160a7d161a5369d4de534b179ea23db6b9e3ae59bcf3c09c9e2c3dd2d36372027fd373098f4df94781f1812d9518bf78121911db5b4930f0c89eca8c5e50343223b6a7189cdd3c6036f6030cd660226ddb7d43c8dc62d082e7916bb1b0db6d4d6e46e48369611b6dc5a2f1fd6dc7ae6c9fee86bb5360cff6bcab4c15e5a74aa553ca3f3b3f9380d07da518b0ba96b6e14e2a47f8e38c449ffbee69620e8ef6e168b63b17e6789930ec383ad3a5b2b64c5965b2b02dc0d812af048147ce6895d7b6940f30426712dc8694015683ee00ea80428fe3cb98b58d00578c6bf0b1b3864e02161c56a9acc01ae7f5883a4851d18b67618665f8421199a9ad6df361ddbb6755ef2d739e2790c322bec7e0ed9c2f66a3b8dbebe08044cfa83de7310274722f5d2fc2adad04bf32d08482f8d989cdafb74ceaf8eb067e8a46b7724f6dcd0db52c8931b1a9bb59af71b716851076963db9e2ff7a4141337f2602a65583f668eadc4c925acc5dd49d243d60843d0412772780a2653b050394510e0c31228a0b42f828012447105aadb7440e568dfe5e05baec0333aae2b802ea0139d6835d07efb0a68bf8d31b302db73a81e4e938ddadb30c8101c2054cac9470f86350ef5ad0a2fa3d16ce4e093058065284c73523add338d9a4ca64dcba8b561ad0d53154dab2a928ac433a42c23b10dfbab48b70718c840a5fa81400c532357d3b1fe53fc2ac7829c3a35735464a8542a958a54a9361d1d3a74b40e2fb98e5ec23f1d59a6836d422f3787f67fbf3e1843437dee6ba8cf8d356c6fbdcb200cd5880f64ecd040e504103f28414d60071a9441043196e860069bc85b0dda73da6fda1341f56f3d56edb79c6d133514394b74fc204370804c066198389c6a85a48a98ab6894852dc330c481c16030180efd81c7e2d839265f54dff0af4ec536c172df58c1b582cbe572b9b21478bd5aad56abd5aa86bcb0b3b30ff96d7412173947f7c0377ce019ff52002fe2f0a7a52b02c54675df77600f909fc0adc2d52a0bbd1fb8a8822a3287806290c9078e3f6649c401324b44b01e03b6e95592663949bf5f6c60afd818acb2ce4193a47bc5334ad8b2e687051cc7ff6b7eac5eabd56a95d1b95a550ff5b49060ee44ce90472925e709038db3040bbb71e1fabf5ae5820b150683c16038ad96cb090e674dcd8fad66c82ca528a5b6fae0f5019ee9e76e8f41b2d8b297e89eb259ada46c9abdb605116b4cd586644d8debca0a7684b8eef95779e64d97bcee7831fe4368f07e7b1fde6fdf3de610e99efb6dfc3e87881322f5b7fec4d4ca7160cb4e1121b27ded3ea6df47fded7dc4f44843fded6ba8a31d6be8c656e164599665db4ac7c94ef5604b86a5623aa6c7981e5b1d47947f039807df740e1d7dc34f3ff35c850e17cd5ec7bf0e6d27732f891320274f605df48debdcf39c522c47a162ddf2af6a75fd65388a1c92dc1ef344c7981f5961cbbfd9983a400cd5ef69a08f1c157a50f4b31ab4dc6bc073ddd94109308a8ee12cf987848d80115e5c44e149e284104674308517ecf860091e44d16117810d884842c9ce1043e8005584162b86c4164e408107148f01860b9820038817f01043bd70821f24769ae04511c040f9fb379d9e28e4be6d21b727a272e592a61a9e8ec5aaa44db47def78bf7df97387c5da444a8e701d27037cb2cc2506e3fab7499bf44e6a04ab7ddb566393216ddb0644c8909c1a1aa3405fb4200486c02fc4c49e460cb03fbf796abce45eaaa9e1a9a9d9b69f53f453247a065b241a6f9822101b703ae5343fc5edc56ed5a47a87c5309cb90571d286f9cd77dee0e43604a7b497e464c9586a4227b7bf53d4ee0eaee2fc849c04c6d73d3dff9082307430eb646a9ff7313b482ffb76d4dced356b415148140245a391e7b5fc376a1965df6735cd6ad68a342d04825c58eebbdf3eb1a4975627bb668e3523efc7c463973bd16a771dd97bd9d7a66eb51880ef82a1eb6533f3ce38db42f445d7a324a53274febc73d26d6e74fbeae5e199b55a1b86ff2469ea3e0e84c1033ff0f340204256644521db8216bcc09060482f307fe340b86ab6b4b86c624ea7fc88d73c4e6a5bd8d2de1a6fb14df6348460d29ffbd20279669a56f2aeaa4aae9397679eeaf414074defba92d08a42e0e73dad65d35ee264bfd33a67af9498bc1ab53ccc935ede69883344204230e9ced19defa31617d20b8c0aa1672f50b02af0388d5faf9bcb14b5dba246ca5ee85421e4be300cbbd32e0d51ac2385d1b1ce89e5bcdd8843dfd253d365345d5c462efe4d0586f473e4c117e6fd1b0643a2898119fdc8cbe8499c147de5227aca55ae936556f494eb38e947445f79921c3ffda0af1e1c6873d67e2ac5c919f3d43e29a01d18cb573ca91df4f6d83dce40409cf41c27fd5f4cb873104f3f9dc15dc21ada735d7d8f137dcca0bdfb6c9eeff9d9fff6dacfeebafbacd3c23b438fda3c5cf2af48d4ef23f8344489eb4f80236ce9a9920f6dd27a32f2e00b857bff1f9ed788258c5a4a622b146fc6ca6629ea295769337ab01b0f37baa774e8ca93ccd0711a1c86e4d44fdd6ddcb66edbbc6dfbb60ddcb6d0b689b6cdb677bbbb15c79cdf9ce09ca1394573da394773b6ccefd1e43faf3672f7d43cdfc7e3739409678c21597978dca58227e08921a610d7df0b819fd771d5fa3f57b7f736f16fe598347935e454d9972a99a6fd601b67544d8f1f14e0d307fc0942000ae2334813967e2cbc592cc8dcd1bc5631fba4429db32ba74205b240e6e9e70ad5b442df6440f0c06cc346e70899fd6f9cce2a93e7e4396dc87cdacf4bb50f4c4a5d87161275db802ce0e48ffeed290f4c93023e851307bbb8895858be3d0ae9cb3ca33d05b8e4ff23f54aa552a95488230c71a452ad1eac90c06038323019980cce3cc9c8c06460323263f5c2fad7b45003ab81d5c06a603670421c30180c06c359b17e7eef958bf269246dc2c793024ae9143bc0b0eff77a5c5dbd2fc3d07454e83d51048a409128257a0b5a11113cadeb7a9cb6698c8f4f8cf1f181a1181fa2075f347a2aa67bf045dd8bbad087c0de8972a7a7fd44719fc7b988ca0a09fda1f3a8642e16253d0bd18c0c0000004314000028100a860322d170402e9935351f14800a7d904278589b4aa35992a3309032c6284318218680c00080c8cc681404b5b220f9cc5e6bd3492d34d766ddd02c8c3e5fa64eb3cd378e510d666d1b230dfe3827b357333cb6b240192658e0e40adc68a8da53dcc0ef3aeb9cd64ac2b925bd57efe8b3df3d2dbc1df83114630bda482d40e9e1b9747963bd8ab829dae53fa6c4e850e6f0e8038763e253f4534041e8b89b2ec538e2c7710d05ac49417048c945506497e0682b7cb2cb558305b36d020dfe0442cd6c589b98d83cb5928168f71069bc42595e49322875ab49e1755a1729a952358eb4391f9102d398bca7f0927416c399688e726de656ae55d349e8083006eaf9fca3fc09962a3cb3c1338ae7a80ac8e6f22c2c4485ce3b26257b273eab4bab644876215b5d49c7543c8589c62e99b76e538bc91cdd84935f78c136983a2e0f0f92645886b678f5c68417adc31aee1de48dc9dfbd002e7bcef52029a641c280b920a09a1550cd8572430c4e32e424f2a4f625cfc67b35627bff09844e73fb5ee50d969db3de64d32a84badd967a3e2295b150359926226f699b81e428533202ccea88d7c44fca73debe9b286d9990cac1909064ae6fa37733ec4edfb9360f483889a74f3f7062785aa73e81c596e60e88c8b01e256836902013d5095ec93e349fe6ce77496ff5902a8a09d68930d87c6750bee58e8d4e62937f7fd114528bc8c2ec13afe011a6b087d7d1a67d4b95dc57498d2ad83a113e2a4b9fc2cf5dab0ea343a9343309990c806b9428539b84df61d5fcc8d6a7576c96c22bfe4520d9d17b8ae171d25c5eca69f7633f2c83c4d4d2dbdee80d06c2b50958f9a948f8a1d488f06105eb7203496cf25394c87591e510a3aec8978d4b0c0ee01e25ee8c6a70ce548642803d21d5d00782cef316e80e3d55ff7f64c4dc40c0e1426a8d4feb5beb29c3fc9a25f817452759269314a45d215ce057716325e084db2414ba0c7019e4a0557cb24649159269c222f36ddd224944e262ce5f1c9d4432653bf413aaf2d49136e2c7458742dc2c92d4a4b04f8fecdea2ec63279ace4149722b51e5a2acee0a2f77030efaf01778e4631ca607337c04fcf1f9bbb595a589c8ce43fc6888a9ed9a6560dec997ff553ef856316160dee4088b4909b5186b7a15a80c7181490fb41f67c480888b974d4a1273388ef516d1c4536b5c9a5c139956d8b579f653ecc71445914fa1ac3a00e7c3e8137f4d50fa6841014a3d84b4764c054306be24302568e644cd1c3b41b30c23fbf3dd4f9c4bd50d5be0c5a0e8646846649f22eea8ca7903779fe88b25ee59c295f5a1fe74e0fb66f85507ecc6e7a8f24fd6beccc5f1ad72388bbd51620d3d0584b8068c2746656dfc0d468df97494768f935143a44efdb5868bda432d40c53bd399b3ca751a7daf4182fa07fa22b74dc2bbc3954d956653a3221c022d66e2427cf1116b4a3542f4d0ad54abe3b0a428d08d783580e265f872ff5415a37cd2c290cf0ab3cbc11dba9f0614cace9a8a6cfc2732b412f8f549490dcc598a78204bd7c5a26db73cc26e2fba11812c67582944b047ced9fc0e53a94022583108b3e6493e77195fb92c049b92c3346e3fa3b10a2ea307ea2acbb89336e2c82f7f90a38218c766726a2b3e56e65f3494365c02100b28bb8c0c40ad5e5c69bebd12d7ce85a5671d7039e71375566f919af789c3c05516778e3629a0100de9d4c0b8eb8bc15aede334edcc4e45dce45a80eed14f8bb0e94411b3c1a9a426b5bcfbc8de7c8205237777fd631ce7f2d493ced6ffc40ed7395f823b74bb58fb866c67a2ccba87e6d20ad94e54ea840493c026442fa60bf244ff9506194cf84d3cb9c66736044027fbc0c2a58cea051a34eb3977ebadbbeb85fbf55c063a4d99a49ad8ef100d32bb6d4c178585e02ec62bc71c472adda82f830c4ea6574786a30b55fc1fa6845e8edef03448ea162239171a5ff2436e1399c22b1b0ce5f3d4a689e1a034023c7bb6c71e67d1919edbc847b0a428f1402e4d85837c5d00d2c6e8330945913a058132eda3231634b829d55d6cc83bfaec8abf06240e6eb71319f4c4edad4c043e5a93925d18bde8e37bd5671ecdd472dfa7f7d9fae404fd2cab4130ae9db3d9a83a6c3b20df635e68d96fc0616306bbfe876bc7f62433b9ba44a1759a6216bbfb677201c3c237605936a03f0b80029e38ec1957166add2984bd5f5f6119db2a9cf1647d10b132b746ad72a313cde214316211d309b0c07168cc0b8a642863b05dff53d18288c33920979e68e12fd252b77c49426e8579ae15e23d660c9a5fc2b8275e72d026a8a667790e8b915c3fea82c56fd4da04782132a871225e13727acf4a14bb26c4dc6559944af29febcb46b0929d1e02df855836fe4e440d7e9675239269554f504b0d4c6edd59cbe989b31dba58dd7d05b3d9cc04874261bc8c6832b66a0d845095245f19f1041b59e8ed39f559d99f35c8b70901297295fda7fb6adefd5db117fe092ef20b0f1e0d74c7633977b691c153178c3f45b46ab2d04d51a390080bf51571d6e611a28a08b230317e47822fdbc2f8e580752eca87dc20416b0918791a6f0343a1caf112c740c43f9614366557f6dcca5a9943157e3a99bb70ca190ac526ace5a055caf112d385674364832b58648682337e17165d2215b2c1af89f4f9144774e1c135399f6eb018c5a6c49fa527af9fdc04b9b37fc9e5f6d6ec6813c334b8ade6fdb9e561efe564685d8851b32676ea0b145fc8694725491e8866e7de41d84e51ab884ecc48b0cb8094665b8689439e51aa1121ced1a13888c2442e823115cfa156b3f854c216171a64e766d3869c96a165fe74872e309c22f94dab696bca927b6a1a2847640a356dac515bfd8dd4e1b4573c3d670e2e98328db213d28594db5bb514b004992e3ab5f54eb324b0fdd74ba9b290eadb5b48140b85feb675ee4e965b9bfe929efe8e587f298dd67ffdfa291f248cbbb0f6049701cf6a64b57248e96540fda2f624513470dc62d05bd7455bec7106eea22ecac93ea14430a3321eb55faddea25b0a2103fa403cb228ad464b25edee9fea2de470da67139ae2c6cc9d9300880f9f5dc815f850348da410f14bc08906395d521fd4e52c486eb64c96cef7e3c38d575c384f09fb2b3361a1b75204e7f5fb464681b24b1c964c7fea2f46048da41751c2ba880dde09a48e886db58fbea804f3da3216a124da816811cf50534b82f3028a881dd0eee825101e39d157c2a2388e298425cfa4acc75bc2358e4f319b4f2ff2922e5b5b874d135efff9a564ec64a63f7b0f5a2b7bd3af6c4d1f51978bc27fff6f0744b587ca7c1cffe999bf6298c9cc8d5254ac1bf008fa2853c743f705fa88c8dc6f81c0028e4cd38cd6131ff14ffb58866cd14e88dde50e09b516e78eb5a7b31a37a90c49d6023d7edc20261524578857a907d139d6797b87bb7dec7e72e4e5154c3919a79c4f9c226ab140bcaf3b83c542b2b307191d187a7af02e1d5c444a680c55c42f61f3dfe5411bde8001c7bb065fc59020ba4e1de2fa0339ec9f7f67499efa0ff46000c3808019f7a04a01b448f198c36e9b8e251e7f3254acb7a7e2ad911d11ec74f225f6404c507766abab8481aa52fbb266ecc9f133b846bcc9427f7f2c9ce0d1c6aaec1fef4068e43525f3dba49189f67ae87229848a03dd352d21b7485a33c70f4243f575937285170b987ad1b826e853fb603a586a9c337b905c5a4446848fb1d43d454dd8270cefe6cbb00f42dc4fd4473f14cc7a5bb301caa190d9f18a95b27d6890d42b89fa4e7cfbb7e7a74d47400c8d9d61a9dd6383ca342ad5991ee506bb646ef2bf107a4d6404d5a0fa79511b4a3142c88a5b0884c626a5181ab542ece0541d39ca979e1a3c61d5fc98c6d2425575f673b1cea9670fa452834891cdd984de0933b2fcbcf20c351cec0340a9bebd639c501bfadfd31a56239f15157562df829a5bbb495708d0e5f9aed5c68a05d19a24540759b76b3c5b40d6e5f3e3b6c10456ed01093abda0189a28f5c2fbfe20fe55753529cc3355c44d67e032dcc8a2755756a32b1b1d6f230b312cc6dd319fc669fe7db9e468be156a14af8f8c4f87a96bc435068bfb4cdaa72fbde0829f3492d353d0e7f9300e69501b7b8a15502b74a1f64fbda140dcb703a8edd30986488fee286a3af049570e173a5be020c674478614c5be9a1ebabbfe2aa49051bff1fd0b3da17d76f501cd46dc95de8f323f6c3ba5795fd32c5d5b9b670b192eab5494c49e1638f93555c22515fcf792ad84f1ed0f51758ca0245ac30521e5821846ba1daba943fcff3f95f732aa65aa812d26d8cf2ed5475106ab5e732ef1bbc8d9cb9fe059a2070e0e34698970bb3291dc03a822f231c92432ec946a76081825f2ed14061e50e8528d5be0e5b0619517e9d9ef95e905d98cc97c24dc84e00bb811368179f898319d2aaf992baa73393ce00fe7c02f7867cb7161214dd761f597f033f1d59aae5309241c8d2250c532b37ea2bbca68eb2b71cb74aaffcd24232bf58919e5b4a247c1c939f534129583c2021a5599218e73aa013d73ea1068169102cb4a9c3247fefd293aeb4815859c1870de770af2ce275e1fecf0160855599892fb51f33fa3118ee08b27839cff185b0051a247a9d35eefa850db992a3dbd38e1024f9daf097926cc088ab191c578a23545c4bb7932b71d1ac33418cd43247640198a2bc905775d290c266dfab2751ba7c0a23703503bd16c2ac4deefd8fdc7d5e4ee4b2bfc147ee8d359b7907cb6a8c471528d2cb4a804a885c54a5975230fa1f25d0f04b150d0c46289e864a41b27ddcb2e1086b3534799f6b0fb2183e0bae2b5dc490e31855d569bb055d2da64f3eef297f7d9736c0b1853acb01e9e31ef7f6b54fe188d43e7fa8e604cbb6bb534175dadbbbb1837a17cd82cb058664756a71e68d9b70db0c4adf7f6bf7d2b22e752fcc0081be352a58402e472b463285d63dd8ab8cecca7f1f5022ef9c5619889dd7bcaf3f869506a855f5632ef2ed7eb7b11987c4a461923723983daec943d648c45ef421a6ac334805c6dac5bc496c83f043705385281f4d0d0047a557087fb74a7c1c18da86dd08131a7a015ccd3405cc01a440728f5713169c5d10778b0abdad986b3ec829aed2eef706daf42f5fda349eac87fe58628655044796c3ebcc215e49d934a7bfd57dabc49f09b1d12f011be0798da76722cf3889b6634d2f8c94f2ef82521d41aadc757f00632a48bc9587c151e2f414ea83c0460aacd23a496795f9a82a62385a87d9132be6c69dbad6eb8398df80f5480a71de1cf099d39e005c46f114910da7ad29b24689854c8f012cbf97c2db7b586a6e38e34e437afcf81a9e05714502b704c9c32dd84ab64fe0d360284f1c080c0a94a3810e0b60a7f049d5f993e9594bc7cf53ee35cd964d318a7f5f3c05e79c604046e82944c368f72b585973e184c8c9401ae15d57bf087ff1d464c9ebdb21f0c02881ee38280d524b22693bae9e902a7dbd414a16a25cabaec532c4fff0d8b6d5e90ab6fb688c902e7381d60e3a8fc49fab03e4080b599407b31de814cc1b9579f7ea7dc4610afa6a628be038608ed464047d0d531d8ec0e8264c17bdd463055467df7b2686ec6e04a8c59027aa86c46a9e5df0482f4323d20e2127d7e6979a550fec363bb19e570aaa6c50668f7cffc3c6a36a344431a5a8eb9abd56bc2dfa77de3801022d20f1e4e732707e796f83cf8f17bb68a825212575a0cbbe13653bd4aad488cc43673534c84de359260bc38ac4ef4cc11a7cd047eb51887044d75a016b5cdf83038f6521aedb681aa0de57bddcbaed920175c1bbaa86b67334124c984286bcd28ace9dd8d85b29f06440dd18dc35233633f439df75def2805dfd0799cf9f87c8d493448157927c073c8a20724d72b426c8824f1b44270e289d45171f126a8bb0e763f97f201fc640953805ca5ca681284a98dd32f10ef3bd62555a1696c45c393aaa0dee02f0ee949969280ce93c4182f2e4db8b8bd51d5a3e9f6f85c34e5086cb640dc7d2c8c439660b3eb3c58ed956d07b000842e5a572c6739db672943b6cafdb7b88e5a4905c24df2de942efae40a9f0c8a368cf1fb2399fcd0b4565e8f9922febf11cac81c3cd302b903eea1c195afac19f40d7f664ee8c1c2b1c467fe34eca5d2f0da2a0d83c02ce69fc166be5b68ae258680e95c8b73cc9d129720ae3e8bac0028bb2072472c3c68884c6053681e515c1722d7f3819a6f88054b91c65d0162a79255c63c95e39472a384a8605648c2d5efe43141073ee5b6ee9594e91fe7ef6a8201d39489bbdabe6ec269d9a4764465b0ccb274984c9e0e3d3682d018d61225ffb4ae14764d8c0c2a518f42a4384b514bc049e54dde2094514c0bcb8522d8aab855aadfb06e4b9f81107acc9456517d22031cd148d449ef204eb4030150641797512a3938b0bc9c9a03727cd0d7b280d46933b2c8e246b508dee97b60fbd0fd94e520ba782bb5144c36795d2cc86cfbe23c0dda84b6853a4313a15fb3ac058acfa771938141253681036325d84b020cc2a186b076df61b30e2564daf21ed95c5de7514252463b3934b4393a1a69e9c95c15db079148e6cafc7dceb86cc49066589653550af01a94ddc3700031578d0f88846eb75c750d791f70c3ab508c6a43084e82f5849132f2480a10bf82b177e5795bb4e23f96a3898caa3cb90a10bf42cfd9a771c18cb7914859db3406096ba12297f729daef7ef68721c860b9e2ac52aea3790ce907910683428cf9edbb902c3d0e5d71ad4936d855299261cd712b7b92c75250e11af43241e55b83dad873736e6aabe521ab418e0bd34c3ef67dc761d0dd544d1d45de63082b24d08cb2d6268f79d8433ca7bcdc97133f54625e6d0dc9f8392d8ffea88833cba64fd450829bbeb0a2e698786f183c4ca4fd4b7e741f41f56a989bfa9129a8eb79ff7fedae0effbe57601dd2dadda6c886a821ca7f498e4c41ab227cca9f3859f37c1fa7a2159301261c14f8e974b70a8a0efd63430cabbb3348875e3181c7359cf3471b9459e3bb1b37d8a8f7e4c6fd397501a53acea5744c712c5e2f89906953341c612447eef4c0e7b59ed9f2038ff8d093aa6e5ec1b96f1cd03de966b3fe3d8a908c69653e251ccbb09f2d3cb77e8b9d05ab38031f8180149c62deab136254137a07451f5690b2197392943fd0f60cb332c780ac63bb741ca6761c9580ecc10d0df4d7ab6f5ee2c29c3249cde8220ba07a440b54e723479f5e8dbc3f00c3dceac46564191c45a5a75c9e54bf8010d96071769392efd718b94653dad26cd21a68bd39d00ec9adeac6d39353753ce773c7bcdf63407096b7b78f11fbf2ba27d1a153f1df68419ad7fb2b9f32fe438aa2cafb18eed9fe9e2fc8a6c46375c128b53483aa8d7219e7b74ce49ca83a9da719358e2e75f43dfa348cf786b7c901d0b068f167974a71ef85f15abab42d9b613d65796fdb3eb45cadd2ab41c15b377612e7c70378db10289730e717231c6aaf4b1e8410161565cfd801fe77ae039f44b6eb4d5609a45a289dd644e4467b40b07a55f4d199c1d3abf5127b0a64db4418c32f52f93018b3338752146524982810eab0b51be033007614d9ca3c92af5dd449c9e978e69556c30c21f092a653508b37768f25b06742bad8b766921621b098b8d5daf815444c31425a38905e54b562568b96edd2d5e054480e787cfc6a011aa83db25833eb2c0d5577f1a4a66d8997311be975b30e56a8d3c86a82ace881e3e68c1ec6f04a27ead00af81e2374eeef410696de52c2e82ce4785b9e9e5cb3a72754acd9045f5e8255f1c5e8d0a128a9b19b477df0d7c6d40ca91b18a51e36f372c871e55faea6c408b45f46413cc397deafb28e6ef299fdca6f55ce7e6eecd5f004e334f97df96fd921ff6b7a5151d10b8d4efc0e512ce957bc56e6c0e7f721ec1d08fe625e85ec3c4d00bd1b8c3dc76757d6f116c33f6d508ef9e89f665c92d3982efab761496dab028b4c90550d72b112b74622fccc8b50401864c1830c2bc9398611c2dbf5b1e8af72bc254aa268e9bff8ce9039af739298ed003322c808ebaf34480d1c13a6c4f03a9f2d3cfb86253aea5c35825174781b7058531441590759e61a0fc7a13db7cd56f53830d9b9a16cb1077f4c8345ae64b316abacf9d1a4ebca4704b720fc7544e38df566db9c74a0579ccd52dadd0a2ba858bcf521f5ade825024fec2dd486942509838fcdd8f49dd9ba15a321f7a49d62d4861e683e31138a674ef1a2901882d1c289a5de8d830a0ecf55023966dcb0fe0378669460f5d5ebb6c9cfa6d810deca5618d5735b2e8e86744af37d0d4585e4f15aa9767be498985b1ace1e24501592ef0d65beb6f2e5e61a7d3983d9e4f82e463352b76520096610502f45f99307ccdbb2f9694d232497ba50088e3e5e76a5e26fc664ab59b1d0f81e49e8d54bf51f0fa1545f833740b4c4518fa86e437a6b9f47c7bed97840d6982a925bf928657b0ad46eeca8c03bb67f633e45a2e91ca9b9d994b47eec72bb13363eb30db9238db70a977494fe8bc4a878ed4e102fed583394986b7ecd648e86572aa081eb0df5d37e658ea4c90b347490d5bd00ddaa3a901f1bbff322b54afb6980063691476a4753f7e8c2cdde97525c398f52354889b712024e12408b64ef2c424826f7578d346e9d29f0c770798d41c1d28d4e8f1dae0c1bfb0e9d026d587021a4855c5dd8301830cfe21b8700e4ddcea802de65f0288a851471e95854ba8b6f43dca659a3ee2d1083c6087d31d3589b1e29af27c79598c4b7619f7ca63cfd034ae346ddd10ba8d43644b181683e8458ef9a7a05113ad8c48dc330e27864857003371aa91b5acf1e79ea87ff4c50b3042b9965ad4837bd8686985ecb24d89ea5e68f8d0f1d5ccc2c46b730fa11e94026d4f5a603b5c0da8c37e565821aca6f19c8ce5981ccdb6a272dcad28045a93475c62d2e270b38aaaa571f647c16bca172bd621f0cd4144b6eb56ce0d655889847767ed4633c2adea0e61d785fa16d01e1516383a71c12bbe08d73c7290cdb727b6af162020f25f105e6ecea13bec5f585bb4223aec4b8929a91382d6a68318b308ad1643a7789c8e3bcc748df1ef9c3d8317771109311395936e469cbfaf00fb0b7b5f495ac5f25eaba9af7052138e2cc04b5da6c667b222cfd931b6b8ac79b337d028a56e76e93c440f6416e2c93c8d536e6a4e9ee27c7410559964a9d480f5c693bbcad3201e4773b6a13714f253a6c4b3cd9afb493bbe68bfb615bba417313b151890a33372dfe06483875c0066cc7ce778097c66f372476f51483f1a827470654122db922082f6e92d339c60ad357b33ff03a35d4e5e4b006262217719798d30b2906ff1c7d335f5d5e19e7551d0f4c5a79bc3c24edbc116996221f5c4847b43cc03fc798c125c7229e656e974bd48376009458b08392a0de25a82615299be37f4304071f9076e375a597c8f05a1a4545c675b1f9aa8547d47c6d4a5807565c828c409d23fff6e94610835fc19b2fd3a4e6f9b6d3675b9bec26627819421ec1420d437834fdfa3660df53e674aba00d2b7e9567be14829d9013ae48b0aa56d60b15034aca8fa33987dd0eb878c941235993aa13741f3aafe5e1814c2bc63a510e3730fdbf641811359760d6fc1f67693cb0856df356bfc40f030927808576943851220516a2e89525b11adc67ab465c455a490e6defb0f3d8753e108ecc664213da2edb457251c42689f19c50c84b7d2e379309e66a793aa89d67e480de06daaec01f4f8a1444d8ce8a57f7691cd03af6d82c9473245230cb797540745ec5f9dd8ce1f4a0cfa04b8f093c8291314b098a88f4ef8ef3e98700fefd639ac1d97e49b17cc68c9472f6a4bba9de5d510966110fb0d464e5e06c0b27ca6d90910d20048bd3e123b9b69327733f14fc5ace278204696197bb5e0d4d4346cffea93356749be7b69e81f6f5000316efd449942c7da8f5ef2eccd4c42dcc49e99cbc9ac8512d32f70313ba53dfe32c760104baa3c735ec398dd6cdab46938e0afd36c09200f8109f6397b631892339d64f5a3d202dfbad638ecd2ab80e586f485792d4b3119533ededc65c5b24f0711afbd7154ab6f87d86437eef0342e58998a6ed48d5de90ec4aa054e92a18f0161aab97e89d9c135b2c3362cb55f194f0e6708d90b36d12b40c7358b7734e1929125f0567029fb2243bc1b0ce75657206815e31ca62c4635abc1a3917e51c934117e1be88bae0b41dad6e0553b77de79754db39496f12f71ceb7dca23e3e2f74704ac21cdd265f36d83453e705cb5298f4db5694960a48e6df389bb49fcbb9da40d36f43bf53e43eb6e77a0841b9e7a952dd8fecac89ce94b519da14393ccd9c10c107ab626f86d1403c56ef2633ed02e5f527be7c3c7af9d63417b065800aeed4cd6f63bd307b0525743d410666cf8f7ce6b2cc3271f04457644a26cc173c983f69944d03538d662e6d84325c5ca78a881a00f338a6d9287e4422682319c3d2dc38eeb2e37a42177789c9979ac762c1000ca2f1da35e466a73ca5e4928d3f15d83cab21eb80e89e1f330f5ef9b97916064a4d2e32bf8004d40aba154e408499ea528b6f897d1366f566f919077f00aeb712de4f8dd6ac3e2ab7b80d657776db871a979ed4860b357cc02718f1e52002b2088c28fa23193f509e2cd8ad06c41f1a0aa2030667868ca91aa94d9a19997b75d282e38c217e860ad8e0fdb5cb2bdaa56e9bbfe77943f9f30efc8b54ec34ab46bfb2fbf5c84d0d2f62688d84bc99f1639afaee5debef25ab74f7e4bb6448362ea59f3bf249c21b06e7f49f4a937b8a236b4458fe87d9492ae0e2935b45274ac00384758f49a9dc29446641804637145094ee13659cbb623fdb7b2316290e19649f12ac947999f19de34f4caac7f8a86c6d93ef95dd52188280d24e7f61e238399abc6ea75e3aa58dcdc9268838a07419b848d06425f3f5c18b3a6618602a6f49bdf1f3c30d999fdc7ded6d3fd65a74c5dad82196249e85b187d6ea1a58244c5fd3ad76cf7052243f595067d3847b5f6b38106fdfe3ce95f3e2fc5101b46c6e7b98407afa87f94f2c1838253c920adf96f55e1a257671811c6e18752756eeededd804638f45d551fccaaa2c9c049d98878bd3a842d1f68248a49c82dc760e90d5b8c3c888f9bbc6c4a68f1a0d406cbb3ab3314abfec1c6e2e0a4dc90e1cde9688018b3a2100bb6182555f4fdc1856b45d378aaec4be917989f65e9894be3d0878a2a8be32f71efbf2a072205e95793edef304a75a24c6ab6ec1900e509dd49ca7a828865b7e2d6ca091102dfd8886358a42f731e368698f8132059ded9668683de359946a3784f99ffa88cd4a7b4467eefba92f9b829ad934b974b41661a4539f19874509aa21b9e0e53c49f13f775b4d65015e91401e7f5023ca8cff4ae7c2e48b0f3603b26b692e8c357c6cd2f4120b388c40a9e2edf496d872aa14c75aac6c0401926c584e73d760bcbe13296fe49f47c0b9ff38d8b2d8baa69191e68ee0b1fa49f4fcf6e8492581af2090d610d9a2ce9d74634eece9fdea7db0edb9ba855581af035711f1ad5aa675ec070e1930594fd473093445e2e39ef090f30161a1b953f4bf70c17d690a9be5a9a3b2e6c94542d6f285b3e8f4b04604130a6a2cfb9b01827b1ccfe20a6df8779861afd355d532ea99e77bdece500893956ac257358023473e2ee2f2ba23955daeaa8ab3909723371a3f4525aba6fd5821da562edb713898419cb1ee4bb2ddd56d0efd05a04adfaa9780a0aa4d54e6a06b6f1d37b21ec094270185b585af17e832768d741f80592fcdf82276ed40b900003b7d8d69893290f32a2eafd495409af8aa3c28f932a4ad3992ac68e862c3ed8757baaf9d0895bab16a05c55faa752597b6b464340cb3f60c8bb09c74bb57534a266359cf9ca9ffecf2cff9cc7ad976c0a915c02b8e27e2cc7964df3cfd606124e29094dc91b7cc3754768db1e9686ccec4a1278797c2232fc58d990cfc64ed7f67cea084c80482d26ce2d68ee8a89cbdafda9d29092dcf65d5a0d1c5cd1352581eb5085cb53c27b427bfa8ac4f9b98f46d18d8b872b18744844e573850d12240da01477e4642e448d92413c670ae8a4a3f4d8c26e349752b46a7019f3a47d56ea0ac6a6d7b02ae33a0bd8df4ecf19d363a0f83b28ca6ab4195d215fbb35fb44a1cfd736efa4d164aeaa3d3930c5786967753c662eb4f264aeda54019e345d27485726413e812b93a0a3228853979dd9565c9457b08a8cb62e4512495ab044c89ba627b22e6b5b02746b96973346f7d03a21ccb2cfde7570d8111ed27f22fe5fade88cd256aab2bcd2c1329a0778b324b931d9b03fbd9a41ec161b860e82859943e64b28e800b28db506aba65c6387c9162702653e47b3b81653a403f05502dfb4bbdce7be4984bb1d3450326b94770dadec5dbfba5fe18097d7d1e850441b65bf7302ce9cd6b44e7210dfaf88a87eec6193b6431d3670f9dcaeebdcf4a713019430d338244abe3509432dd97d41862940c0c624ccb785f0b2424ddf59d203216ece1de9139c111d5bd3f90532ac23c8e134fd6cfaa2461eb8b21a178a1ea9c87ab905e981f90128c9bab4a4c1c40251fda8143b5f35d91facd50c4481ff3ac37d205d943c5cb8275a15d08076cd972ca228246d8c59257f8ea5b933a05e09ef06e62f108f296848cca9d82b66d733541c1768a5f21dba2dfdfe1b400b5b4efb94e15b1a255923db020c17cee8a8db3b800920cd933f03613de04d33ef5cc1e4ca88e63a6142dbff2c5da9bf3c340302a8f36b4c7521f13c910b2c6a0330b6638b4ce235bbc417ee7be721ba19876387b78ad9a1c9b44aff526d9c965860b404e46add946e6ad1df54845662db207a7fcf121799f2a967160c092036cf1dc73f45eb207a4c12403cff8e66ff4851e449872ff2fdddcaf84f9a7488ca34bdabc770055a34f1f0dce637f39441bdc7f728f0159e72c4a6287bf47ce2f572ec371d04ecfe133f4e1c7b02969b6962843468d012d539b6b51ccebd58db2f342f1b8f44d644a9ab3668c383f8ef4a45b4f67ce006254bd79537fd634978d30a6bb4c33a6839f56301521b05ca2a6d4e7063eb1c2d4ff822c0b4826f4c64f058010402eb571b7f80995ce52348be374f0721f206e39301cd30ef089733f0ab146ca959e9a555d17af546a3326a4c20123554af6f9cabfc0efb8f00408276d5f9f0d4b56a8aa019cd716891f47270d61f5c87e0bc63a036f36d7df1a18a962808678e0ce3a5fb0eb74845ff52950eaae3d5606663d24477e4096375ae12a05d21c6f79244275ada5174af269c7f57f97eed1c66d9be2fc3a6d8aff60e2c5ce3bbb445809847d2d9a99fe85e9cca4f42b487629fe7cfa6e82d10891c538ec3b8027769eac83fa75bb5a479b19d7fa401e7113fa938cfe4f675c8afaadbc9a69ddbcc9e874fc9cafb196c5e3311eacca2eb2bf328e228797ddef29273be545903003ade81e59fbcf2a11d81921024743f301eae1e90650bd8c3331f093f8a375cea55ee8bcfd0e788b85a25fb5e500ab64def3cd92b7f07be1f445ac683190a3002d9212953c8802078455d43ecdd5b0e6e0aa7882c1269184078f8328be6d485351d5502cce6fbd412886548010c32b43f47de36f9a5c2f0d281b2b6bf25b2e7014c60e7a5995eaf96855106f697a04d5ea492d8e87b911e33b9730f2d6dd9c7c44b18d2daf2cce4cbd6d2eeb11f03cf99b153a1d2552eade32298672658f5e8c12ccf50dd36957f0c09668c8c7ec03c56dd5885af5093ad69462fe7dd32f2417d1cd4a121b1b9c010fd7ca4db3d156739afc5956b0b5771e56c59fd67a635c773e4af3c4191f2f0d70eb98c454c86dadaafa997719ac723f6f65865835205752593adc8aea7326c71e4ca16461c8641992c3cb3274109cf515d99cd9cda3d065ff07ffd10ecf589a13ae6aa6003eef4e74070e3f0a885b2ea132cc8383a6f6784a4ed0e912c6b41b095000e2c8388f9670b5254fba4231f0e3f616ed2ca4d4559ebeda285f9f2c66063c27c2930c43105847b22c29000f43303eea39a5f12da726441509837cc9e26ff7e3716cb9117539e7195a7302bcf1b93482357de45b4f77a793c5349ad454e15ec6bee91b6c1700c5860ee778d16cd7a3af5b8a36e9768790440b1615f05a8991271d5d57ef1301464480fafeeab943d7d31582383945f422c2eb0d5e8adb477aab441077b34761da4b9cd730c08a2cd457dcd7cbe2052e439c17e8b41de898b335a9b0aa9e31a174cd9e15c5f091b3d02d4978ff90ab765d26780ab00fe4cc587f5edbf85b067be6759b8ff3212d56d3980616218db8154d06a1d4a163f461919026f3d742b1dcfa703d456f2fa34cc645b6810da965a76f16af533ba70ab78efd07f300147684df6cec0c79c56234794592d3e806fa1ac675f1387d5277820ebf07608eb29e360857391cd83c77682ad8e828017c425d916843aa0da209371b8299ba1f294e6d4825fe46080b730fb86cc060d3cc2c42418c8551241dc39b602354a1202b2df473a126a3046974c25cf8d05836823b3e9c4d843360fdc5fd5ef25cd87062b2b92b0e37674a5d635ff3e58db1024bac7a9ef7c5708ed0ce3d9846e5d1a089f8c31c859f5e656c892f212f416b4ce15eb956dad2a0660b485c43a854f9be623436971defdbf7331d571ff7cc815930b2c776892680cb1d90910c1c444410b0d8409f5725aef7c4d1268c89b6728f01f4648b73a3ad2c4a2c1930251ab8d5a12fd65d5673d05501b7112937e366ffcd9406435d19b6d4b6c7d592df665b0f2836be222b079e4582363586fe9db8dbed08a074c8fc9ff02478274a1a812489c359cde6df5e97481ac0a90500c3032b89027f2861b13ca1278ef10f9e602d40c8bfc57bdacda053db10dd2c6b62e2954857b1cbcb9829952a2e6b00628426f53b22b041f615726f82ac670598cad4d04ca803f4575e9c9fe6dffa8c220f9e20364d90de67eaf407eea6ca4f13cdb48240c9f1702e0a30a455338cbc068666f80f03156a179a98cf65529647cdf26a6e068ff2dae76278c8c85f56d619556ae934ac2cd92385fb5e4f4620d57ba662d1ece5cc2c30209b1e94a07c2239cab633f42aadb40d1b848bb26fef282cfa7ef57f76c61c880cef7a0168f351c8ccd8b425ce616b50791fa2da1760cd452bb59c2c60c1fc5845cb1f1b5e8faa7b0f8c67070fd582d2d2bdff8df6d6421c9d2fb564ca822a01dbe538545392f87ab8cf2ad3be1815efa8f41b58337f51ad1c67d1306b3d01fcbbad76aeaf0b3d9848d9f545a6c6827b4e22a005a20f097eba06089100ef949d17a36f7eb239ee40cb1a35780ee39b5c53e426173e8f2dbc3babfd008e7914f6cd1e54498e148187bf25b012d5c7f0eb2090ddb951f8f36a01a1f09537068a4d072ddf371ee6684d31b22c3f13469ba509518fa3a99954ac55772eca5cbeb22ad7e321fe96b09441a617cb61c157b2e1cc80f67ee1efeb616b2daecaab9707e5fad23dad6dd18856c012bceff01480b7934153f122b6ad4579ee71a362bba2cdf82509a8db4fd67ae36f8023d5b03a7a91efab38b081c375f59220a60c3a39461984f0502a7214cea89852ab33aace085e11a185665ee8e618ab3e7984d722faa67489f79a651a0fbff8035ab8a79645d737f3224104f7b1519133832721741c08b35622e63aeadc50361a8709df3006598259007d577a636566ff766be70844dd47eedbdf6cc0d7269c9e0d9502ff8f486142465ab72a4d7b5f6816eae59d8e33c4517f098551a52b63dbd556cf67ed7c1109a0a54cea89f697a9fefddd08b0f4e0333e6ac79e896ac1c195d45a7ac4f4e4d2681e2317f480ab471367a5995bb6fc6d19610c72d7ef0755e32139d8d8736be9df7997affa1e5afc5728d20fa9c9ca6409ba9587a60d099871cae703def82cd99409866ebc37d9c1b8f510db729f19a3f358478a3eec285ee8cce3faf2af48a285af253458ce95b8f0b9ebdf4d5b8b5d387b07fc6a6ed1f02d4c622206bdea0ecdc5a8b11ca0e7d0165873e303493eb9a6c7da2f3fec31409b6dc5f08641a42b53705bc82cdb06562e73c67573ea413656881620d1414647e1a2216aaf8dd1753b9742c6402751b5e26edfaa90525571f53d738b6951ca5d64530a0f22c3d1b6be0de0d6d4397b8766ba615114c4eeede03bad671feb6b86821d545575e18289d4123cd89faa803667809ccc74449f1e18b089a826379b784e21046f081e182caee05c653225b61f3e0f9abb6afa3581f696d7c0c7d046b3c0182dcc8688acc81c36826c98d534c754aeaccb0e3142fc2a890a36892b17d782044b9471917c90dd3a8fccc0b1cdea722debd1af90c6cdc1735f813fbd429dd75af8bdd77bb6e1ab7ac6ae786bad7027c8f743206e83bbf5060e2aaf8f4989a8a9b648bf0ca68e74695fc0606b276e47d5ffb2da06a6c31e2f7855f92e853c83673cc4c067679f25ad838f2ed690bd63a9f4bafc42006031e8c842475cfe711667f3bed9b1d85b3ddfc9dbd3ed2bb8afa1ecbc7e6c9cfe342ac5086d716572e35280649624866c4faff2692a67ebfeb9764425f8656e4f9bd17c60277486649c08289bc5f8dbe5bcaf443c9e7f8db8e31d9d294ada1290e5345ec3f18af0b598bc155d3f3b2ec75e8833afdda490e9f947e11939a5e122c301bbe0663cb5105a52a570ce08ab1ce5855424a08c0882aba1706473c44c48575d5605f848780813a23045f57a103c069d4d12e39aba7e6b0a9c02a652b6797a060acc2f8850e30b960dbf0b4b438304ab6fa7e0dcc59133390d8cc91f478fcd63ec196aa25a157e41dc1a710298b2a708966b0584e28d1ae1c280349a22c66b2430fbbd6557e233f989614bc8fb79ce5077aed9d804a1a8e7a387fc23d4aa81fb1ce117649371c91704786aa6a65e0878abf9bdaca5d43e20a6e7f01dbd8c8bce09437ee9fafd899461568145e2c37c7a43ac32e2486ba0f138a9670aed744c135d3687ef09357bdd1044457bd7ac52baf13c77c9d6baf7cfd55aebce6a297c692f6e65d77a1cb6e77dd9dae4237bbed6ad75deea68bed527537b5120596f9076d332cb515d877337d1c147ed27f9cec77aeeaed1d1bfce072a585ce39287ecece856464dfe73442df7c1b24a8df7cb7f48268cd9f030fc6005c36eb98810d712b000a3e0b26aab62a81c5f8f4eea2549554ceb9bd7e40b84825d6c552bade7293b768d0dae1f2639a9e9595418470f9cd4e84cb07e7b8af4def71238d46d58690beb5cf1c76134c892004b2ff6c1ef28f60570e5d0e30c4b5831f2104f6eb74ec292e0abe6ad26f2ccd0f76fd2e63e8f1f599a8ad5e9998d6b722fdc26ffc7ae81281a8210ebdfb33ddf2221ea6c2e689c73735c1aee759360386deb945068d65fe2ab3e8a806e3063618a791732c55c8b25e85e8b2281d85c494a34db4ea169249a93eee6ec2900f0a837401b935c4ce0cdafe0c99557ac103404fd07ac0b6f6ac073365234156b0cc3fbb051cfa4b7166caf34a2f0754b433ebb1b364a1cf77862ecd1a2ba063b8cb5b19ba21490daca4391c83d44099751d69ce7c30308acee3341f48f87680cc4c8a4fb84a263fd68e79d65d1f8d0a24021a5c643eb861bbca481475dc1634d4b003f121d4ab539e3a68e61ad53671c21d70a6ed7b75b298ea1617c40ba905cc95826fc6a95c43018d8562974a77c46b1ca33564950bf5dda91de16a40482db2568473973dc568ea8e2748dc4aca8536e80cab8241509e224550f7ecee1b67dd005087aac127194905e822f414da0c7581327fa65906674a9853c2cf2f19b517f5809efce85cc3dd150e5a17660f262dd4e43490ea69f19bea1a5606bf83e7a443a27e1eba1dd129b030f059b380830550c3798e6fa48c46bc99ae39623819129df09a61839ac945b452a81cbc79cb6a4860ae396c39bace4a4398a6108f2333ee04142ed46e59badc6b327d2ddba06220472b7028f308f4e10752d6c1e73fe3a500e11d77e56f2bb50198b35fe1c2f02cc599757a4788a3d2e99d1766b383fc9537c8771c963c12fa85fcaa6099358190aedfb6be394f33d0d352864aeb2e3a17b0fbae9131b48980d5608c22a4b7934cf5d33515caf807081884817013538803a45e7fda4d18587b69996c088ca11541d0d449604f521b767569b0f349bc2c84e2197f93512f671a5dee23f3b86de1ff4cd99624badee75c9d3451e444e14a51fe1e051beef3744edd0e0828beedc371a537cf9406c518b38ac5556071ec6907adb9848885479b4bb354098c07da6d0dfaeff973b528638c7f1a2740f14d06562f8b7ca085bbf373f54e957b956f56ab6ea7029b09dbf0d6c23e21fc05c4ca06401379851700017158edb9afa0ef507879d5109b7e65f0ddbe2948ec12fc6da51018637f00c1c526567d71a0874a5e5239371aca33ff6d1d001b1100ee19f2d8b03b53975e5fdc3dc5360ab3aa7a376ecd0cad9acd735da2562f140fa53bde46763c0bef5c7def1fbff28a9321fe6ef8384e37d0270347f0447c8a9a68c07b606911ed1f284921667fb220b3a6f7b50cfa18b4684b162d373726dd8320e3842709447a0542bec52e9c9255a77e57b9b30204de231f7a77f0183449a899a4290c82afad7199adea6ebbf6813612b74dab82d0b5027a9d8ded3fad20584e69f584271cc1c2d789b6e636adbf7cb780e3615dab7f9b6b245c7bd5ceff621c27bb715b37484fd71545132317a6374871a58d4c2afa5e71f99ae400e1084e995d383ab043037568c5896bdea1a29a4a15f325bb77a3c3ac8b1335715624b6bbe4ce09c2d1052f7f1669ab87809350c22f770d351f4c2531aa333d4620e7fa696872c2af81a426cab900544e129a5bd93df614ffdaea2a460c5654373bf57ee01b5a103cd86ca25433d0555cbc0e710031288d88d5f8dabfb31b07469cb691cd08dbf96be15b7cc7a73e4ea20b3f9dfbcd7701266c492dae64ac3f7121b23116a3156d0d81054be6f42289511992717198cd343479d44416485a0420cf522a38a58ba4a5f625ecfa5364eeeb4f3cb4ad7291a2ce96eda08c74005b71972bfc766e58b8521ff81e0c2f454f14e378ba4c061e2b58f8f4ce5f9b7ee127c567d9187468b6dc874f5acba749a807234b774473c238270f933819c447e915dd5694ab2ae0febd9ecc3f2bd98392700d9f7b26bd401e2a9d215880f28a6b29ee27737d2647399c1a011e10ba66aeee5b5f0f10eb9e87e4bece337119c961b1d73400671f47133060110b77631d43f8844333c30190062d4aae812e8c4f2703ca9fead86210e56dbf9637c63ee9a27ff18aa1946ffc48d5e4b545d739a1dfa66898c436e8711488c738aef4d1ef8c0da652fc7d9ab8010ce88bab2ccbe656382372251db130be9594c5cdaeaea66b4e8afcb84fe10e3de93203c1f4e9d8da73d06635702acf94eead99c745a9e8408b65b259e33579c1791c2686b76cdea8d7ec0f58da99bdbd743ee1b17f7e384c0f8f5538475cb0d8f4a076840abfeec9b306e917b86f85f7f844021476d496a9e4e754c010449a65ec335a3a26f48aa4b59cb2ef4336f270f56a07d62cf6ae7dae8b39b173955a0da2aa69c143a6313fa2d78ac77c0a141f28ea20acc1b16ff469f9b70954bcdcb25788c26c17807987a56b786d5347a54f855dafdd609182364bd7fa21d65af0df75bee0b31e547d007b821cd6a0266ff02da877322e370f379b14fe0fc8f638812e4bb02268fb17af4cbb1d9139442ddeab4d771eb90530f63ae1d68caafe927dad7c1b072a415087d99c7c0e83d2869ae58cdbcaa396c2f961a75db28d2874117b87a9a3ec035d6a28fc19ffe62c97e37a6056adc8e0437101490c678b5980e0df6ce1053c0006c6656e7995d7871d4f3bfad48dfc6e8e1d343961b3b3ac24543bc9ac84df0e482b233e6289a77bb3d711199351e814094dbbabfca34fc3823c9d62f853318b8f3dc4bda2c099843e0b7135500d2023b8b2e978cd7c25e3c32a7ba03e98a987effd21af6c8ecb6ba4cdd7885a69dff224115fca985f099626f4cca8e79e361b23dcbc6605427669645a942be1b0b1dbadd05be990c1b765740f462ceddc13509002c4027d31145709cf027a34ef722396f759352eb156f9ad5117c8e405e6eae8d18264dd40a5f2371f3b227a10e8053156a77d1f00fcc722709c868e6e0dc0f8e8e71a7d836e07a5d7962653a6a448152c470ee9987a99c846fea95a91270e16c5c845ae8cbe52c36036797578e7d187559455c24a9cd3293e88d187f5803870190dc07d0e50520d19a925ba20ffce9b944a6a1ae7fe2926b47c07f5394360c00d15df0df76c460b4bf7fe43e5e305ea26551196503a8d0784b8088a85cf9b7122a1be2c6b4d6b8aac6278121bcb6df67433bb8082486767328026f8d63f14432c8b9d115b229b046467776cc0e96d8593a4f0996da2dd44a3d243cdbc9ba74d0893d6124bb07f59dfaf9b20773325a214ca8efa59231f877de9de1c59b23685ab9c2004487be2caa590f65134ed9a4146e5c0ace8b1b27fd3d878fd076224f76e8c9103278c5cea5e9be340562d778d83d292c932ad6a7610afbd0fda0fdefeb6fa40774d4bce56a84c023961c3b162e5a6f7d782dd296b6ac8136551dc82c37a6948291e2910822f490c09ca8f8cda08c9089898cc00f62d2f0e8945bea351db5da1fe41c4b4e4949e486a4eb18c1e121c38f18630f447301d61e031b5799f1b6b2225b9c125fca7d5c4fe5ec015f0aa06b9fff76a6c0994986927eb9618370abbfd8644bc299df8a56249f290fc7de633187ed07004d7ab92fe8f09b9a28076aa0a73757448586731066c2df202cefb610141fac16bd0cdf1d946284113464d5d6d5bd06b94a12b7999a7254f593e88b4e7bc44d88936def40acd80beedeede25aa815656f340cfb0c092393362cdc23ddebd5478a5d14be8db0b85bdc414e917fd2520fe6a0c0ac02f2142c8446a06539ede84fb50302b951422c7786dd36206b32648004da3ae15d8b7b16682b7eb13fe4a4941554444f78e837afbf6c443436f290761930cefa64dad78798bb1ca3bef54dffb178954819fdf1741e4f53cdfd89c698e3a31c610f89dae29092c8fce941bd21b1ffd17fa833b263bf262d985777bef2b8f7c98c22881f8451b8ac9df03cff692570d7644dc250b4aa0dcf1496755dde8359ee17153309ea60d9d102b1c7d74b16b2651d264f0a3b775a90193266f62c4f14b1a31034ef41d963c736df4b00123ccf8fc008b262cee2317935ef4141c60287777341da011d5bf2ba2d23e2b3dc69942a2790085d5fa65116e885496502214365f126678f1e96d903b9bea8710cab4c0ac64885df61de06e700d507a7d2f22f865e062a590e5e078581a01ae0f58a3937a78611815a6da8cc1de712ec8f51864981d52612ab48413c62cdc885aec5b71a3931b48eefd9d708cb747a4fe35b3a0aedb3e9d7b26a9f1484a47a213867f15157e2403d41d90a3a8ff39e3ab738a40617a164e4bd71b1c3a75c32dbaec4717db7febe1653bb5d90c335f0bb82fc81add0c1fe613974446ce24bd48407f4cef23a60063e19778b25cd3be0af5393a298326c5b12c2a4b7935d50589736b494582fb786fd62316f6b7bba09294bae4a4b8224bf4102eb067e5595f095566c18b94932aa60638ae5a299d8ba8948c71702a356dfca6319d6787ab9ff9db2d196480bcf7483e6357d8776ce700b045f69a3b83c7690c54bc45931ad9142a9f500838c50828dbfba4829f5cb96816d9d38cb851625c397352ac666f2cdf688bb1eede289ace80d276eb225af4f4052a29467f7a24b77106fb2154fb71da3ac694ac3bc46906e54a7f695fd341297e586e5053143ae22600e7a82cfd11a7d7facaf1c23e2dc69544eec7f0e511f7139abed2f6d7d1f7fcbea18497f9484f5e0a38cc2346fafbf033ed286707bc5cc97a621ea77e3406f53a331fd4fd5eb643ec60df558eae1fe4de08adff937978c3bfa4d2f20a4156d952be9ab4f51e9719e9b33d34f2061d3e2f956255b4b0643851162494906402dd17b2c04d68e7ce5a65425914797640ed91e6e3d8d7aba0f4915fa4a355572013fa18abad8f70fd8645c27f86ac80b8ba80331e79f9e1901678656590c93096d635c4d64e09e906ad87a1d7c5a52d031d15599f01b8230f4737edb7f2623ae22047fd8c9074e9b9b8a8d9aae7538e6ecf1bb772545c8cd582f76994737d691ec8372e2d8e755faabf95d01566d6521b1c4001813a1ab0c46237373a21bd0f1236f64fdfd28e5bc23dc404d993227c1f98c202d34ff83526de75088dba4de2a58d1a430a089d0a745a0fbac706f41c9e9793f78b8fb72ef8903927fd72404a82a485483f00a60a78a58f8ff68f798c6a3bcf834204d7f2736bb1a8ed93740136929c01c7b789b867bd82fc98f98a6ac2a3d55ed0f79cf8ca360def2e3d80af00dbd51779ed732fcbd7a8111b03b4ee54e62e55a02e0e5724b8aae7e77d33b45a3d5f9d8aff55c988f66850c27898c244a96dade006d7023e2b4d03154cc7474b206bcd7436619a9fa207788bca8e7135b9918960fa77862db313d6b5df4ecc4a266a6d94e1bc08b8bd534e71cce7a9b96f63de77fcc5d03b177173f37d253bff66292b1f56a696ec3ebb9033ce26c7a5d2c6bd21f9460064bb072e353faad7f744275c230602f3dcaae68f8db2c7cc4c2bf89d486a01e5c51c260019d4da82cfc490f5e785eade26108d2d1b556156882370157483ef52011a157cdac2362ee5bf03ab90ecfecb1604a61bd72f97806eb811c1b6e18613b23548d57098079c3cef0bddac758303b7cdc899a4846b68bc4966037ad4282c74a4fe9bcb558f6c19a662202275dfed490b128b8eb1e261800a65fd32b34a42daee25859a1128a16cf86c9d1cf7fe985f2e130e7083f8b980fc1085d6e25971d84adbfbd179f5380e8a008326f9518866828f081e870ea8c2959051c59d9d2a18c09a5512783089f4f217cecb172b12bc4a6f0d52e44998c2162933044d5c9990226567513e2ae98e73f7de67a951ca41ea3fa2b9f56756c4c7f26e65f6f3eb7c9a20a261a8f3109bd858d012f94bc97c83c5adbae4a60a8623e91ed53b3fa2656c787abbb85240adecc25864256857742949a14ee16573de3287bc82a0b7afa77927aa5dfbb0b24d7f69e13fefd474bfc24a36ab909852a79d22e814b3b1b08c7c2877e6e827640a2f04bb516a68334ea184a91ed2ee04ff2220ea692a3b0ff63327800d2d2e2a8e4b04e60fed3e8be0982a0f8e5d9691b8530c1bec839063908bbd72bdfee210963e6f6d23dbb79cc95804922ede2c4083457fb4a490a9a494281257639a7b03926d4a25d8a92750ad6e0801427073545729249c0883f1d5820ff4609196d804533261b060b972c326e480011b4f2293e1eb847b41253e301b9aad19e34e448bee8edc23a9a054e9fb3f5f72e1d8ab2eafcf2c094cefced2a901d55f8b45cb225627c56787a487d435eeedb3b804d2da19371533fb831aac0f7a7bedd93d875c38bf8f6538da0d0eba65c784184d2adf2bee7aa7ae4455875e6f9c252956f195b848bd5982c58fb30da67e4ad2bbb7e7ca1e6f3a465c4e676a2b93c1085f8a80132bc7b6f7723dcbe239e72dc4fa5862425c7b12a86f53f5b0772614b08c4ebe9ab517cb5df3e0662c1193c91e9666cc3a6b9148b01db6bab5c36690f081fb48a552bd61ab73fe65d4c6192674f08ea8c764c4d993d661f1a52835ff84c6d68bf0599f829d77d4938449da80cf9d8853f9331117d4ce9702a03a196a1aad3085225d0e1546adda52ae909df79fb2658d386144c5d92345d52bb65c7b9de2c973c8dc5ea810fd1259ef7b45e54fb43aa50314931d8ad7abdeb9ce3da950c38ccb7205dc95811c4f0ead999453fa945b5372566f0ca3e3f28ab221e8f848911bc0aef239ea692e142f136b7f53929ac0b5bacd69ce6fe8e4dda0b9acc9f3bda2f5624aad4dffe8cf701d514be8a54745c014fbe8cb22f5177a146530d2de29dba52b2e882862a038868e0deaa235feb14a99950b7611f72d57d57944a7685f34130029d2c2c3d0d5ca9ad3bdc4f4610311e87258c19276bce586058f440be6a48d470f51c19c6639bf595ed02ddc0011b17d1dfd2aa729451092e3cfa3ef0219a66120162b3e9795f55232fc6946b0851c82a80efaf14d8369b9b7ea58d7823515f01bccaad2e71730df20cd8ea1bef4a5f5fb2abbba4f41664434219023fc591030e8fd533f0072cfd3668f2b2bf38a385a088c004c384f3ae8fcc364382f489c06ce60e8c337ae0b60f34eca2b8d124c6d312d8241d9e7df9598b1e147c3fbbfc8f2dc6a45d8fd0c048b4bd6e24b77c04b71e04a954828a5a330c97dddaed9750667e5fe7542920ac295ed4fb5866a0375b42a7575cc3b5cb684fbc0aa74bd0e7b62db9faeb9715b3ae306a35f3aa0b1193ca07aa710733ffd64e15224f9e2d42ca6c21a022578c55c675987dd375eae447a7a97084a51c7cae7c74459df4f873ebede1e72c68a31297ea5252289d28e6155b040509b739e6421143970584ffaef2d56ebcc1b1ffc310cd20a0fd7358fd488e83b89dfe410796aa3bd3eb2bc6d55fc3f2a1e008b6a6f933567bafbecf717caabbb01604a3b6de4e75b1fdac45d55b94d68e2e36aeebac012eb6efc752c35d70d633dbeb3d9183bf154f538aa7b92ba5742e2abe7af501064d5f7c38a364fc6eb17ea66b7621561d64ef0c33407fc65235f0e434d8630734191ee9933846be042e0292ff9d19c7aa26e70073879e3d973565d800d794459521686870de57888251d4f420577ce7ed02acc8f87752455811e46066c82c33ca536dbb89f4e70ff78e5c443f9bee14b7019f7e2ca701b841ccc9316ec08cbc825b1811c624babcfda0206aa85ddbacaa4e9c95274c5f6dd32a4e32f17678a0eb4989863ed418e4c77c5c4245d3a8dd7dab49385dd3e663f046a890c6eed0bea8ca312a6f49e939f6ecb408dd2b2900168c2d0c5ea9e88cba7495d39ffe9a2263ec651c2bb5a315f6aa708e2db2b89dce8160216a0c4c8bd14f3d535315c466b018210c3d31f465f869431c3ca9ec1910028ca80df90dd27cac9eec6c0da18700d025772775c9348f64bef1b49f51e879c4dffb8be41922f2e077f220f95b3aa0aacf89a07f82c9260dba7ac62b93aea6f1e1bb659126337b324d387f35118c0c1162af24fe000b441d32d9203d4106e5e4136d05d51d7490beb06768e8d9b5b2035bb8618610f79cf6a10694af2eacb57b740ecec3d53568a56c5da2fe2431995134f044a4564637d5c4aa0664fb577917bab48a6d1a57104f831b68377b2a9b5a627bc54a4f6b0fc263e4e1529b9c888c02d9aeac42549e8c5a69033ebe45554fa5d10af5336873cd23fe900c95969467384b30e5bea78a01c0e35beb13dea287bf67781eed641d5d8d2526a017021bc2f242818e29b872639ac057aa7e2e46ea196877d18205185fa157834eba6ad321bb5af0bdd536114cf928ece201ae1a2ef5a3af9d0085d7ee57627b12b2198f64d39bb0efd746a1cb4b0775efea93ee4542c9a679faba447c74bfe2b3b42e96183ce537a284deefeccc2bfb085c3033f56739068fe18411f7621b6a236df094aaf6b58f050c3b51f34a8c1476b00bc2555d60206e516de08af27a9ca62d762c8d1a3fe0837404d6708d77802cc1412de75d72e8f083353427bc753addaa56148b484e5b0e9a11aa9cf657cf452059ad3e53037c843048cd7c3cc09474f92d98a32ab9b2706e97b59af0e75d25d516481bafcc0f707cdb4d8590b9b93d77569ab2167620c0ca03bc97f74c249724ca79d174d1c8286f45c5e987fb4f9d9b1063aeb974a60d8d138c45111388298c162ea910f3e216970fa7d83a79edba58dbdea4e72a1ddb83c175639a9f7e14172e18cbb1b6b0bb86978d7fe120f34ca64999a5d28d9da402ef527e39c0a8f19f7505422a780ea8d40af437387032fa206aa3558844b3ebb7aca1f0e2a720ea28749ddb6713294c94e95d7fb2b93e877f30f2bf0b1a1397c5a1ec15058674330b27a23d02f1fc6904faf7eb81df8768e4f024da7cb486c0420131f72ab8dfe66b24b192800bc9af657d048f680d101284018a63faad50d7bf93053cd74842180ba4b844b8012b67553c0f3d6ff81247d43500af78ed6060cc6cc93b54ec6f5bf068ac8d7c22d260ad21e59895f6113072ad942ef1cb74f2cb7c4f54681d2777c9a2e195381b99ca58614a05f757f073b7d0d1c8919fef3fe2c976e69ce8321b7ec3cb7401b7ec0bf85e3ee5bbc857d1a4ea49127357b76db4ed6d55d43abfe978a0fd3232bde81fa9c95759f85d1ff6bed3de8568d9849454e9a695f83aa4a30ee8d1d9cbf9aab470121f5744a69bbd937d0ae1bc5c66196afa6d73720229d8288391dea2387da23bb31fd4ecc820c13ffd6711d07d6a1809f95a5978128c26fe920bf9cac61404d428cd6f81aa48eefd100324d3b9882eedf4e2e612a0412cbbfc4726bde32812011be3a6b7a6a7d9688f020e0865aa2889867daa37dd522de90a8e012a6c8a427ed90ae0127b46270ae16bd9aac28ccb58078e0d921a80b65ac7bfe782706a269fb508ffad1b14581af3f9e5d25cdc87a106a8ec9370e59d164494588a08b36af4664dcadc8f844c622365973505939799f90916c3855240d0eb050ff76e9d86b9945e9a0f4d635985b8642d4ed3904056358310e2a5ede0e17bf2115d3e009167bdaf3e49e15db9aebae09c60f21552454a101bc7a327898689a98521a494d5f4f40a8d42d7aac5efc9a455f324a780cf2f2e5ce50cc707847e7426d8853e26ec1e36bb514f140dc4b167a08656cdd91d5574b995bf402e552f112a1822c3f26d48a05f6d469118bea3afc5099e8c417150f5297f4c822d3e4d2856f87cc3e61935fa52003ee2d9ca2a6daac4eff6cce7d48e7d58b6f002a78bc7894cef7c50c8f8b1eea8c03bd66c8af5944d7f9bcfe4ab951413ae5e446067a2357705c407c93cced1d2eeeab4d8ce0b58cfc2ea586d9b75c72300b99254e30fb9f2489fc2ba69248173d9163c314e78c8e8bad6f99efd61fdd870199786fb18161d93ef076bb2c9b0ff7e1940575158d1242a9c67d9224f727f3141b7da3d828161dfb8d7ba048c2396e8104dc9f185abc41ffde9fb7ac4f14e78622cc614aeb47eeb208263048d0018bb17fefc78195a571c96211fb06868157dfc0263bf50d0880bff8966bae74298941f2055451c9e5e82ed69a1e6a56fb72d70aa33ccb12ae4a06fce06a41c4f4f978d7b9c9eb2ce73c7ea22dc2cf9c2a30b2cdab0aebf9d1ed1a291c898dbf6b9949aa17195f49f4271cd061083c11e153e578c7445769842d27e59bd106319ddb61d9a38bf2a795e63142fd9f38780277055685ac643179b98a3168eccf73d91b34fecf5cf3ff0548b119389c48ccfe81d3e1136cbd14362dbd60e74caa92debee5d50d522cc5e87f43c367b1c4e52c9e04f688736904dec5535a832f700c7899aebbd991e76afaf8400763ac6481d62cd8afa001f666ae5e376e31fceba6a99550e4531ebfb62f933554d9698f34ce45cb5a91290c015a36763bbd0e36640791de29c9110ebc6b1070b70badef78049508f994a296aa943b14a1ed4e335bcf4196dc1437e9a6f348861e10f4806b926dadadddafa197fad8dfe11de132d49d7a3a5922de0973d3739dea620130f111f4766b1dc63e0cd71730d77d5c5d04082de72b36676ae0e18d2796f41e766b6caa70d82c26fc2a6a9921e7e0822f6ea6de0a2c9973ba550a4a33b7b4fb21ef7dff05c318863995491136cd6eef5134eaed321db667e18d57d378c5a3adbdaf68d5a7286e6e4fb8f41641991482bdd056083efeb13be655f5f6afdf65285059c2ce9c994c2391d9fc869fd5700d7fed3890ea7bfedac3dee3edb0d7efb6f6c699c91a7fd66dd5ada0c1648e6991d472a23e936532b5ed12542a0f9279ffbfa569e7676e4bd2b853882d960561124c0b7602b103e31e98acfdea12f85dc222543a5cf2a0d1d416add68958224063163da7d891e682eeeefcbbbd16e6f3fcaf747de370c83450c5ffcc77bc9e746cd2e6b6c0e4af6c83869e66698a8ae686d7cca7032e793b0725cc41b7ba7a83f8b50f15ceb4ac5868d45b5293976aecdfb843e01eb21363419335903d84877581e899ee055c8c9611394f6a7bb5f488d55a630bb034a91f7537d379cf947c38016552a4330fd6eaa627e6fc07854db4127d0e1771e5f005147d3825cb6b9dd4767913ca4960c78ae1880f47ded302b56f77250fb723a846aaae8ebe8be67bb701fb2c46605f318219269fd9d1f936d07f9d0b3d9243a0cd2784e7e32b5e712cf8ccd403e604217f96920fed50287d8fdc28ba12a34f9433479529b469ed41964676ca95c5bb4e3cc5d6072a0265b81804d5508d23c05a7ce011b3870a7949d4ba2dbeb42ea6e1950f154c1d96ea27c84841988e1c992f573e43b33bf162b80aecc39e0e9b44e00894e67b6ac1a47fd73761f904cb9b0a780c695df2935a4711506bdec6c4e7f9623afe6125e104bed3ca06c4bffc59048e680bfc8de6507b20cd5eeb33ad1760a17fd0e7f4969095867c12b22772d7cac632731f3943c661913f6d5f67d3e630ba5e25ac19a95779f7adbc24c6ac86a37685eea61f1d8225fc2169398bcef1f30c5f79dcbef16336daa7b14f208780a6dad0c678395a60d5ae4e87a45ddd02da3f59efe3d5b48735405faa4be847d0f935b2ff2633abec608117d12fcaf4c63df8bacd0670906b87f6e35f49d7f629ef5b6157a4881acd0ec872cb602db9069405f5a1a9f77748d4965a650e90bfdaf92a97d520198bc9d2de7a0c1961691d04e613762b79eae46b402c55354f71f4744c3644c9c1c8f9c79614ff948a33a689fa3a0919676912259ff71853be4e315dd4b76435f210e789d1f58e2738d67f54b5c0a77039a521caa45a5cfdfb8196568dd4a845259d8439955545990c43e1f9b0b0cca4c293863ea38f295c2fd8eab38d9bfd7f464a68afb1e48839fe0d2ad901ae1218cec9407f3bfde259270553e8b08dd9e3ffa32544aff1671eebe318d83d82665db5844308d847498536d018c96c661613beb2cc92ed9630766d2ea9726080aea40066eb27739a317152ca94452f6fd1e41c0a4c3b87aa02c2e7cebf39f0cce2d642eba94542da773a67446b2aab4851c348214c8b0c51ca564909c6851281a5b35082a019b6f43379fbae3cb794d47e66a7d239ada2b6301f69d7b66fa97ca923f8277bb09a7d7bc66a9eef3cd4ac64a1eb22051180e761e324d90a0ce9f32991d10790b8177dc8c65e4eb2bb7474e2fb7a0f87bcd974771b15ca8102e124a1a1c9a3336e649cf8876c49a179f8338ec377252d56904ff200f03f0fe3a54377909f91b62cc9a61f490f4b8c09927dc3405a57bee6bdc9dd7ad5b8292b6cb92b4702f2c742434005ca0177933a230533314b8d7206ba81df857a9ec7ce82f76d8a3ba004e29db5e44f4e5b08670a1552339c5a81e7ae540c6964964e58d870253ab5b05dac986c18b010cc0377c78692728f4095e4acc57d95b08caf318c6bea42f643dcc6348568ce1966b6fb6d122495782850120b7788cb85bc45af115a31d78c3881744831c3ef9a166298a5b3fc0fada51837b0714cca2ebc3605f1bd54161404e7caa0ae0cd4d501bb2640570fec0a015d1bd07583bb7210570decca415c31b02b3580a1b7b5bd88c06e6e4c5849a9177439e16bd525c21a35f122c7aa0da694728b351dc523154a44435c3ce8ad1f0674ede0aeec9a2abb8440f19e5e94e0f810186f82f1402ffb3918d706edf2131c9e78201ef8b94a7ca6443cfb363a2d0d358d13640f0549cb9f7859a7dbe18e84b295dd67438e89827f8c48eb5d6cac32340c619be19060634e7265528459d30021a2233aaf940f45c277ddea98adae59717fe175541465196fe6fde619cda4dbb69ce977004b38af1c9e2f396bcc578d6f791c26eb8a854b2abc2bed57be23662f360dd9f24de2d1b282ce856abf93f622c5f4af7a21d09203467cb60c7dee314ffc52a47f193435e3a103739a05271d306521aa7f00a5b8fc3a9a57970a757d84a586a8d244310535d55120b192e7e954373172bfec598244248814c68b920eae265128935278f6e452a17b0d33a37015ea768ebb153beef89d247d5d48c31d95f905f278bf553ee791009b7e7a6a624aa3370ec58197da1f3e114e4610bef31411c264259bbc7879307b031e6554caa472853a0758560b778d86d051d1040db8ff13868c782fa8a8cfca520738193dbf8559f6865100a06321921d7fc0670c632fda66ff5d619970240eba08286f099891047884c5ea13da3e6f29ab5de91ffa269d09db9947e7ddb49f1ada721b915c382356d7bc10bf5c21701028a471a71a91f2bf0bd66f36a6d27612629860b24f88cf1926517bde490cc2c58b7fb778d9239014180e927f29f2fc29933f6b1773c698ef1cc5d86e756257daeddcd9e7845fe8a1116b273c4889b83fd102522b760c0362f8c3b8dd127f81dd4d50ce37560c3840c6e41608688edfc78c3348abf0a45de823d4a80388bc491d6a99714af6ab074f309de9f172707aa0904cc8abd161e8eafb633a141aabf4cc67c4c9632ce6e3a70c3ca648aa0d199f68aa2c244fefa4540ce9d593afced69d670a93ca463f410c89f6bf20e38d3e6301f4c3040aa52fb85a309a59a503022bd5a09bbb51269988adcc54adc38436d062eaa2f12e466ce94c03bf9ec512407b98e56f2fa458d4da5187c3f0bd59e78f54666733ce60f41d49bb40c85f1e65b94007344d22b0212209d97bef2db79452262903dc0b6d0b480bae769d9f1da31058fa0d6f9c94052e3d7aa63b3e1a94089803b4a6e6364dbc2d85057206293f8f35ef9df4a3b2abb7dba8f20b695844fbfabb381f3f212c9032b494b0c753b79976ba422c03186e43e20889a497f19c73d43deadd04e9bfcee5b0bdb003469e73e9ed34879c4fef5cfb3c9f6e33b92c70fe4affed87cc4324f570f4df9c0f39af34c8782f871bfdc2eb1789a5a7bef9f5b9b94db7f436eb5ae6e204702c22ca21da79ea9d739ceca6e7f98643c66f5cd6808c4bbd1ad7694b07f52b3be772d81e177297fb7e0091f15bd6808cdf7ccb1cd85cc8fd18614c9fd3a31761687eb54ffa0d65e35c59b3c0c59aea1aa40d4615275970032a3634872f62229468d3a0f4f97833bd5b33777f34b77000f23da9d7190427456f01031cb6ce476fe10396acf84fc0a114fae82d78a84208128a29c17c2cea81d20b217d2c1232fa50a3a2c8073a5f249453e4839f36c2e8617c2c3282e79170010f123be420e10224ed92a14da2cd596bad5202517119a6c1a64a2811dda149a494b5ee4c1e58d3f7647af2548908831505cc82020e3bf6dd4f1aeca346f289252d19b5f0f82c8f9fc327cabb78fc16cf19625242caf11952797c168fbfe2f1531e1f7b7c94c73f2df9ce959862b09da09ed7a47c8e2654fe7afc2c7e05fa1c4e947c539cf2ed8e2ac22daac0e155eaf9abd4aeb06153dab5f44d9590b42b160929b52bb4b029ed0adbe797e40cb15df1274d7cc5a021a376fd340d7db5dfa4aca75dd115bf1c663e9ec39b04861c5ea4177278a3b890c30c082687d9d0b77278755e727861df9ea3c99276cd233e7e8b2f4793254d7c9af834f969d71cfaf8ac2f47939f26b126b128ed9a40f8f8ab2f874f94a4a429ed9a467c7c972f87cf149f259f25a276cd1f7cfc962f870f9191d193764da18faffa72f83c393a6a6ad7f4c1c767f972f83435c9699223d4aed9838fbff2e5f0111a1af2a1d2ae59c4c74f7d397ca8f830f930f94869d70cfaf8f8cbe123c547c947c9074abb260f3e3eeacbe103c507c907a94953bbe60e3efee9cbd1a42927c7c992764d1d7c7cefcbe16489131f273e4e84da35813e7ef7e57022e464c8c9509276cd1cbc93244e789cf04869d7c4c1c7b75f8e265294949a5069d724e2e3d32f47132a4d989a3039f969d78c7dfcfbe570f2e324e624d6644abbe60d3e7ef6e56832a5c95293259d764d1b7cfcedcbe144070643d2ae39c4c7af5f0e2748767680da357fde095050500e274ada3585f8f8f2cbe1a4e7a397c3494f4fa269d4aef85409251f5a98dce1b13ded8a2f3f164159f261a61369e85113df4f48691ab744441aaa347fda455944c0477c7b8c4a45dc29984a689bbe2f4b4af9dd2ca34793e62a4d19a549d33094d2118c2a7d5bd8b41606ab0413df2e3bca1051a65d073ff8ee22d8ff44183e3d441a171ee3a1ad410c1e5a213e7b6873f0ed5769d2b8c83666871878f8ce79063e2c29f05b1896026ff9fa759f8e354d7cb5d0b77f14c213431fb26072c06e619306c66d4c466092e9abbe7d356491aff2e5d4914d0dc6bcec9f8eb54f83303e95a9c2bc327550bbe255ba4adf5da794e9349821f9769bc1be2d5368614cdf16066b7b041cb60fec832ed696bc5f3ca1c789181f308704da03660102cd01e31b4825dc7d2c62520379056ef958c4440548e0958f4541344d2860978f454140810a4130d18324fab1e86787125c5aedb458c59aa9897184168090650838c20747d3652b9d316a66f4e0049aab072df03e16158105155293b3a6b26ecb6cb2a89d73cecccad820ab523bb3be75d3e69c53bba91d58735e39b39eb5d65ac339e79c73ce4993d92b2f9ed315a976e79c736a21ce177184c2a94e9361c86846061950a9ecbb7fa78e0da4f04ae6752c59f659292ff0df8fea99aac56566aff3746c208557dd712c29a54b060364950ca972958e6a092d6ae7e138d2806aad35a334649a4ddd54a599b5b5d6fa5de09b9fda0095526699944b98d0a2658a315619da7fdfd1fb94461c7036650fafd37b78a511072ce9d62d51a669aa77f61fb813cd92ac805f3e161511c587335309a7904077807110aa28c2065d037cfa585444cf9c52c4cee401ab3e160535cd2c60968f4541522002a73e1605215101e38f454146334345413cb587cb82832376f00f3b2b38206207d5034be1c08724272062180729d8f1683084a2c243d793748a420ab8253d1e1149f8b02d41ea88b8410f9a12c7c30b6ecf5644440e30c85e3458d2ee8c1556602fbe1dfee126b56006414f92009134846406b0d84e4f8c0718d400a92806db21889d2913ef4dd00e0faa50e443a5c82788221e28b59c98be3443ce9842eddadcda4a2badb45ea799c665de4d96dd6fdb8c80ed9625ec08ecd3a5d174199bd60832075602fb31f1be6623e0ad525a378fd239718431674419fdb3c75bce061ef4b3c1460d0d4ea7323669b8ec5bd63aede7bcda74b9048627caa7632fc718656ab699f7e3807cea9249e7f463e265984f1ed386dab18c80eb8733be85363873c072faf6f2081c9b73ca9ae9a72c300bbc3ca41e01552081d773ce395fd42d3e22c00e90b08325df5ea7b36610964af073ce39e7a4aa17befde221e8a0c937eba3e0185007b0ff7e7adf1955fc2084279c481103a42982f044135bb8e2042b70c1abbbdbc9b715dfdddd4de5aa3eec7e4e9830c6054539a0e2a7e3e92c2012fc74074a0265600421982005195c01083a884f5ce1b3821e1f9e2baa6832850e3c2be0e008df4eef6f4ba0d6b2667b481b05d44024d3370d31a6b70322997a075c2c009ede5581c34ed609a4cb4db970665583dd7dbfdad5daf59ac3e9aad6be3b65f0cc3156cf72d7a0aabacd754eeaa29336d8aa76356e2983745537bb8e9406c1557d7e36ec6fe198aacd00f444f3a5726996f9e8a311ae99b917d6c3613029a594dd63ceb993346f4e0b1096334ca5eb730ae1f661effc9c54e8e1670d92730a5146edc155e2c3ac870a3ffd7eb687c60a1165369fec0748b0ac0f7e7a4fcfcf3a54f4737a7881f85983e40c968928430fa20cbb840863fa84d21a0d7068a1fc74db732fc62c96b43d3f3dac5b883440a2cc929c010351062b4479c552c8f25158f1530a58ba475bb3207c28756cd023c2906ebf1bfa7597b42892e96587fe821b6c29b30a77543a95523e951ceb08dcaef3334e19a65733bd4d17d18e7a379953cf469fecad5a76b2e18ffb83fabc7788d387dab8cebb57de3e31c118e575dc9669f50e69d54f357bb684ae6f105c8c09e9d0b7ec07f4cb738e9ea4807bdce0c37399c3363a8a512a7d8c335105dfd131106790485a3a286d30b6519491797340fef4b9f75e198444ced04924925377123943134919dab34e226d9f98a03c7a11902f542e62e475ca7467caf04c1a1ae4ace6a13d13c868c6e6cff4219233cc26538808a39f05042c3d9c3ede8e57acf2387428a322b10d119625bc5cdc884a88d710d28b442bb4e0c3d2cb483ca20654a078b9e4217e6c2e73cb7abc0125a53cb96cf1d317f27894cf0fc8cc4fe75abe1b80f88a87518af4995b9eb7b0c757b9e7f7bd6c93f21bdad5e22b2b53bae72d8e5a41adb4c81629575a3e7923f5d168b00588976d86f01739edfec9b94dc66f1f8c8337599ee310a5dc1065c8db51989d6fe9e85cbacc3f58bcf3f85607d3951695afc83cbde5f3d152b97c554b956f0022736013c80d0d4a595ac938cb9373a7cf5b6add689adf7fb7b94143891c180e44ec8785a49e50be254c26e9f17185336e4869cdc468b01d072273ce68d70a1532e76f87231254c6f87c58a37d1889987c9412c41351210a4e90a8420c8270829710a4d05112850972b8e2c5e241102ff891c10ea438c10b07e808323881931c1835a10aaf302efd9ce0b3f71dbeddc6051f8b7e64f0edd707831f8b8480e2573e16097124b96eae1481a3f7294726f1232ba0348190d2c7a499d155224de30de520699774b96488f634f1a1d4d188b0850fe50e118ca4ec7929a58cbd6cda351186945e83f4338791c9595bc051de1065bc0c8f6d9033d88f4c78b2849c5718937e49f4a68f8eda01962e67882f9d075c8c096b8c3fc819eeb76f728621d40db0ccf7dedb3344cf0f228832ed522862058ed72bd2648732263cf4183c555dada9691a1749363b6cb82105393ea5a657ba299e51c1ce03e131a46c1bb41b81c31a6ae79c53a6a646073b2ba0028e53686003534c310515173502992377e69c9d67e3d429241cb202a9e7dc05c7255183201ba23cc54212349062838de1d89d96a1be72dae29ccae9cde1fd9c62df32d7282b53c5b6e54d09c69f0dce5b3538540d9ce7dc66a2dc66ee40c91da2abc5b387d6054f2d0f9f87d607f7d0cee0551e5a20de3679ea2e3e9d2136d7c15f2edcb1a366a4b7c32a168cc754db61150bc639b73766284775bff8dcddf310f95f0e3747659ee7fcc7e62b49a74859bbec73d506da0cab58301e9375361d399a44fe8c303820311b0aa5720e6fbee2be244e9deea03245d22e3c9f8043b0e55b39872987b9d5571341ce73f6b96a73f1c58800260070171f974d81392e7399f23448398e9b91f2b40be5ab9619cf791e7a8ef3ef8b9173ff62e45cf5c5c839003e28cff9ddb6d557d320e7311ae468f464a0c0217dce8bac88f2a1dde9796984a7564079f954c9731be7d5bbd9f24c835ca63ab2d2d9d1a29c3e2a87467ea2dced0e50bbb8ec3166477d9e671a4465aa835537d5792cb262ca879487a75d21d551d2d3ae70c692768534c9fbb4abe5e10d312b5335a879b63b4c5cf8ea13221f678a24890df3b67278b14b0e714b0e592ef28cde32d569574863df41d4592c8c83b61cb354d7a0e6590a37d839128dd22ecda9d3242a850651ef084505a6e7a206702c9a02fb9f246220f49b7349c4e0c96fce85e06f7369f3d878f388f2cdb76df3e8dd6c362d009ddb5581e3c7a601805fe7993400c8bdd28af00c1b1a2f78f46e60709b9e4972060c4594114574b9d3fb2cfc2c0f59cff21869b228b339cbfd9b11dc1c7fa86f73007c9bbbf836e73e946b31a01cbbfb5b41ca6ff207c7222a849e737f2eb512131313636388c163dcadbbe75052e758dc51284c03ca711e22df73c8f9e63f361456c2f16444edca4e3d336a1a174efdc5ed4e121eca83dd858c4f83d463befe69907a0cb4b2d01cd84079408d408740811093688ce280c630a6314c6398c6fc9dba639f396f1fbd0d6ecea35d2a1aa3310a4483503936b8b582c8e77c08fdcd83a07e430d915cdef28e06a9cfb770ab645b82bfee6990ba7fbd24eb78b42bc4ce43556bfc880dba0b914f81649cc0b1cf3d87f755391f0140e322002d8820f5b04e9a00640b837c0323003079ea3f04cd6077ecce66c39fa3dde613c549af0676192770188d7e08918ff30fec42e457a0414a6153863aceb0962e56ea9ebf56d220f5f91675eec301c8e703f6e5f052dfbc1af53dcb588139df9cc6da85a2e17d95bfb8788b539e95d395e34c5f7017be50be0b8fca76472bc29cbfe0a1dd497576c7ee4419495e50c2e2e15df110c32451a08f063548bdf3b89d49332fcee1c52f641a9ba9ae41cf4f1e52cb439411442e718e92346cce39f5746c6ea9db2476730e0f918f73a8b90beddb379dfbe2c37c216e517ff9c2aec5b7c3816cf585d4c5ee3cf5962f54a968eca9df983433284b0eef4a0e716a263df5f6ee74d6c4197327efe3b2efa30dda4fa5c2c996bea64105c752e14e4a29a52c929d529a0017605054e0e921cc5bdb0dd9af934aba5a6bed22102a0c9640a930613d3a5767add465a78f8aa458a14fb12229a2d045522475d6dded954ea1ef763ce79c5ff696815101a63011059b137ab4a384a1232a9c6c0c38e2870a5708a2882b98783961084faec0a28a0b928a909ab0528484254746141df93441937e778ee062cc12eeeeee225fa907220c99a39028a35f5a2964468ed1e04c834998f2a88f454990c17ffd1129d2209d3432470039e0ead646cd4ce68743166a6c441ad47724e911918aa288c5a228723e8c52663ca9e425071cc6219b183b5a67fb0be9b4088a294f1d7c1169a451488421dda6332b5a11d83c462a46d0b9387faf76efe4e15a112822a7107b6daeb910f91468495f05bd8aac825edc7741cc92890dd625889650f24b48f958b40414181f8b94a0e2712ad2cc04f1d860083de80009545eedf7de7b1de763bc454a44211ec599a9848b31327a3b7e07a97449b34f19f9ea8a80b1749b4883c364a7a987209648dac10a2fe937220d8e1142b8c24993104028b1e2556bad3d3bca97a15a6b5502e86badb5d65a65ad35b662942f39b5d6aa0492afb5d65a6bf5286bad5eabfba895d2aeb4d6eeee9984207ad02e2075d7eeaf35c6d439e7ac996d89cd39e793a49f73ce39e7b474524ae99c34b3aaa539e77ce2f373ce39e7cc2ca594d239e7a4954e3ae79c3473faf438930579944a2ac252aaa70841330a1592c46048be0e38223939392378f98349f5ce3929addfff71f25afde4a78972ec9daa3ccab93bdfceeeaed33c87f2bae73387d72b4eeca35783d3380f42b53ca4c56b6eadf470dc0f42bd033fae9ffcc7f5c9518eba75eefab5dfcd4134ea2b2caa54aaa565ba8ae50b8b8422f8e9a92fb4f979f2f89ce38f3be57c8c3050393ce5e95cadce799dce19f9ebd3eac42caabc5c9c04b0ec7504e7e5926d0e6f952cdeda881f24fbead3e757e3a5dbae03c1897910cd43223ff39016ef7938efcc41340fad8b5fcdad5f9f5f78e774b13988979998c0c58fac56ab95912338be5aad565c8c050845f0d65ddca65d562e2e2e2e2e2e2e2e2e2e2e2eac1c63015c327e024b0f79bc7559ad56abd56ab562ad56abd56ab55ab1f291951b71c9476294992e2e2e2e2e2e2eae43cdcb251f59659cd8dbbc79fc4efa9d34d2e3e6ddd6fd686fe1d07e3a90ec87ccc791a9823597553ebcbeb9f46e364b4356a54a952ce0d0727899b8787b4c60fe72c946e2d16b88160efad6efd33fc2f4d26f9223485e0ec94088f31ac8de3aceb787e3f4360b994f43e7e4e4e4dce936376088fc2cb770dca70e247b9b87ccaf9966d9d27989a31e63b6cc294eeca5c7ef3cc8f4eec7c9739058e599600fa577cec92fbfa09e80a347ef9483c8a779ca743be28b5066d9f170fa85f7e9ab720e627d7a57432f87b7bd1b1e3ac857ca53373c5499042a2f82f3523967bd1a416495a71e5b28f706a98d160ae5284a5df378f43f824c0f4f3e643e1310a83cfe75c94395498093f3224111235580f00415576c018b2a2f951fc179a9f211163fe5205f8813fb2eb7ae4fdfb23778b30ef033cfb291ca593e233267a86707a693c3085e2488555e38c35abc54d9042a3f62e4088ea752a9148b04b2ca6b88ea2a255ebaea3b62040610a46812042a8e4003a457ca71982c21072d08d183931120e1a552a95443d01c238c5095f113d804abd791222658bd8e1851a552a9542a954ab1a452a9542a95721d6a5ea97c2404443002063e80a20a3ec45e2cbee2478c1cf980d2113f55f8208420b0f04ab911553e825d47470756f352390c7ca95c07c67aa91c765f2ad74131e5e4b05e24c89a7474005f249859e4bc54ae43cd4b958fa45890405194d4c31150207ab5b37cb865240c8c48820aa028a20913bcda531fd6594287064f9aa2387ab1dc809bcee5bcf493147a4418b2ba4d57d7917424a4a42425298d3ea4304a779a4a33f552eb3438e7abe67a3465260e60ed4232854e403c9132429db5ce5a6bf459eb9c73ce7066e6db5148c0d1efe760adb5d65a6ba5b6564a69a0b1a047b7b5d65a6b6da552b214d02ed306e92907983a5d41ad55e626f4ac2d4ad45a8764adb556f9d55a6bad95de202c12a594524a29ed224e2808abc7a61e80ff51f36db0e3fb60b2f9cc87510aef38468a5cbf9b96757480f1d21cc6f2ba33f453852c9f79f46eaa8759d54094f2416648ddc6b8859f5ebf08bca4535a9988c0485c62e715738e4b0f515f7348552f652e129d3565541fafa084073ff02096745dfbe2ebeae8d0afeb3005bca46b1f4c01af5bc48629335dfa1035687e73911b4586a8e1bacc456e9e353f6f148ac5ed01ffb0d7adb5d686d676cf69ad9cb2670eadedaeb76f6b53497477370a093846afd189287bd8173d5e488df9fe80ab771354d1ab6f21c61863cc818b9ff9cbf4ea1e0d35ffa8be65b5d25ae9f4e961ad744e1b16f9da56083fb2a7cde1b63d37bb6ba320620a537c183dde1eb27c1bcca69d76ce9a79cc6c666dadd46809e50d5d97f1618dd5638df1e5f404b787d6d61861c40260edc72590a8fae48f5cb2040e553c774e97b019a3df9f8f7e97dc1ecd49d57ebe3f15ab4807420df634c771425a2371b791a60c754e7309a47d5288096b1e24854efec28bbfc0e2210bdfe7b81cbee4d8e00b2e788b1c5e8d2547cddbbbd1a8834893a67bdca64780639480575209060f675e3c045b78e8979b81218733e04b0e416f91435f71560e592f646f19ea4286c9b0560e59cff2cdee9934f4e690e54340c1d26bdf6cba99541aa4b149d33d53863a8b75354dd334adc8d3eea1ddd33ddd036355893f9726951bcee3a4d2c414a314c6aa92058e455124f9b07b62394f9d73ad7b96b4fcc41ac8c5a9b7500fcda32943fd65b26ad09cead09cb934a94c269cf79c7ab1416f1ebde479d422cf23569e47b87b220cea282bf02a87118ac7bc88d1aeb8720ac2df4f174ef5b17cb741edae7c21eb531f4651d8fcb05d76702887421ac3113de1b28df85090da2593e88bba14e286a064ca87ace7244f266574498f60da004e82b82ba43f2608b87aa51ec606a35f1f1fb66a77cd0d84f5d6bb57f59b4320accf9c3ac1b1bf968dded6eb57cf22d3d38fb9d5441a9c3ce827903e367d2c72e2e839d4f51873bb1d2db85f68fd3a57bfea8393420d4697443662d3ee1ab24cca9aa3f51cab5f577da11c8a992e2c90413336d13d66fb58acd8a3c1e9493e7a8060cd8d18345eccccb0c97a90f4f4abe31660da8e728e76403e8b0abeeef9c981749f4979fa6ed651fffafdaedfcbfae4132221e090555d131a923e358730315618ad9c13e40cf2afc77b93b67661150bc6634e5ff44df36cdb9cfb429ce7502894db888fca2dec3f324779e6d8bbbfee7da190f73cc779afdbb66edb72b47e6f563b698b6083241b2c3d2a6a3182b1c17be517e13c666b6f23e07af56283f8ce9bdb7160df378d83e42b97d4ddb9b5f98f76fc1b0e5cd484298ff3b67e9bb59947efde0f48f759e69c2065a02f75a48c214518526bda9343841bece01d7007e21d1cbd48cb7a7c5d9f996930bee2940111d01e3da7b55aaf5996f38c182e3c78a65df66d749817edaad35560b56930fa107ac0f363d1112ca689267a607d2c3a32a20918cca08a9a38c14b3945ba0a091fc2c02cf133869f4aa8e2a7b39640f4d31de9053f1d449a62866615f4ba4f70dc044d0a25377648268e402f66028b2a7c302f08c5e89b76950541507c62caa78d4c3bef26dec4971c396d403dca9843e3120ec738d48414906ce10947988003093c11a5084c20f184931ac4a224347df4c8c2d98873768843ea2ee05eb0c0d31b7cc9229eb89726bcc2d196a15485733cfaa2051cd21570d1025362079a503246d93e240338df3ac06fef0cbac4b55d9e85d1c7a22c827e001f8ba6b0f3b7cacffbc7a22a489e9b2e001d6c2737bb168e8eb4669953af0e88dfddb1dd6670a2808b31594cf9d4c7a22c90fe5e0fade6f58b1f2a0b1c5bd5c1cfdcd60c3618e38c5cfdb22cacedb2653d5b079f7ef496cd3a40adbb0616b87aacfdd5876400e7e34b0f87f51f36b7ac570b7ecde01402d8e0ac6990c6784a63e02168b4cb86dfd8d1ae968ce15143dfd0ae76a0fe621aa4dc4b0e382646d2ee8e9436c1c518638c20e0a20f3a549448f96ceb3c1c324fbb4ba95fc7d7bbb93336f35a83e050bd0650cf5838a867e057af19a6c129ffde4ce37a35fa33ffd1366dd3786427a384c35e12f6922823f3a9f98fead7c3fe0982fa3a3d1aaed3eb54fb9c7addf1f1f886c8afb986061bc9a97b45e7ba29bba6a710e76f34385dc60a4c9d46bb5e34388d7aa29a70d8dafc7a6bcb3a40eb46e047b1146ec7aed486a8596687ee0a7694cd492cd1de4e4619b2c53b5f396bd5453943124a765ec6c7247a80f22ebe72ae45ddc6095c3fedfcc9bb93774adec5bb961677b92e9300a2e70aa28f47f8588485a5e7629c0b404c0c31842efeb9bb10b35ab5388c77d3d2c24396b7b4b05a2d2d0efe8bb35a5e5aac5a5abcb05afef2e2ac2f6cbdb4f8e4b73c1a5a5a5a582dacd56ab562b5f06858390bc6abd1b26ac9a10b003a292305a31efb8b8eb6ab8bad9717d66a65af8c48545e8d7e568b5bafa9e1408b7739c4f9b0d5b9388b77e3925b5dd6017ee71d27bb2883e5e1ca5d9ceb3ecd5dbe95cb5c01e3afe5e2d8a357a3e5e2e0b7f0556eb9641de0b7607d42e8b7d444182db915447e8b0fa1efe24150efe25de72e9fb74e832ebeca43bc1a2dd925374ca6b3439386c5c2d8cb3c9539ae020e2994cf320fa90f479d70f999a73c1aa85b28ed9a48344a94113f73ece9a06e5de543e4ab72a8f216a74fda1576f4e8e49953a376594f870422e7bd065c3fb9c5d1f9c9a3674fee33334e864e7e7c1927e0b0777cb2ec4f2ea384b1f79276fde8dc25ca6079e6ed4394d162e59973ddf7e22dbe20a8f716befac297dc22b33ef9288f0617ef9288019277f1ceb9ee5a7cf15961e7bda3c365e5f275df10f9abdc79236957d8e28dc400dc0164e8649411fd6a2e18f5c93841777e344f8359276930f396ded1e171f9e1e22d9e39aac54f1e0e97165727a38c1a3f1a4983990b91dfb0063397fec9383165a2c832ef2599b792768529e73cf3ee69d78f944bcffce4e9b83864e6663dd929ddf5ebe2ad0b2d6fdd8b343269ca646ebb28e3e4d666015ba7f625bb7ebb76757e9fe55bf9427ae433c7d7a6a76748680a5cf099a332eac367999f3eaa136164d9cb4ab8dc4a1aac49df4abe36985469d267beb9d6cab20ef063bcf4188f5fe22ccbeecd31dbccec5abb9231394b4f3fe7e9d73b0d1abd18f2e9228df59932b0498dba259f4923955aa6fdf6c5392c9fa40f2f351ac12379991b49157034d29122a9101d6a1775a16fa73c1d63e740e9a40a34581da5056c33db1dbbd6aefb748690f2e8095116b02462c2d14349c4235dee849dcc014b4fd260953cedf4933cf7933b381f3f943b3bed4ae27937d7a90d67bc8df1f6451863edcea457f36eecf7a4e92b112889583c92087505b6117d48283808a9324742a981c68e7ba3babb2c924a7c65e2ab5fe9c4574944d439a722c48e76dca84e5c29be56e1eb15be4220cad41a228c5a6b751bf161ed42128976925031aab7c1ec8b5fad95951d8932d553f0d5b5eac3d72875228ceafdb125b81853eb8976313eb6100f44199d479f51d3ae98301a355218956c3e1d94ebe02f541ee274726f9efa74ca5d96655996d9747b5ebe0dba1061f4e57278318be53974102996d3a06c6aa00e62612cc475db303303828dd4488dd4d80672a8652a876d80abdbae85daa50105ad4481fd45003af8877420335abe9ee51fd72d50d3dccc0a1cbfe60e6a705aab65ab63f3ccb90e85c8ffd1e08f5a4fb7c19a43e99a5f29447ecd2dd4400dce2a969870757bbbe8d56a33efa67a0bb5ab851a9cd9965b9affc802c801b7b68ca3baf4ea12e79b4a834c587ae640edba3e9b4a83424dd34c3535333320e80e04f4b997ae9859c78a98f08121d949d2aee9cdd3edd3ae56ed9e9fb37f2453839369d2f4d24b0e58324da9e4920a4b4e9935c69de9f7eebcae072a1065a44b9993a68676a17f8c34f405a485bca899345dc51cc1cf69829fb25156e0e9311386894a39a7a44d2885b7e3baedc4e97cd8d9af818830a4e34f2e913e0d4a5ff9e451835064142539452e354845f650ad6b50a5d1a0e550f27c96659206a9dc519a342aaafa62cbd8cc8532054f3d942a784b79d08c0a6c352d1f4d24da3a4819a8bff49108833a8a0a0e1b16ca1d9e250d0b1b565d228932b81444192a90336c3498a6c5a9d3b8d13c6a6897d7f98ad3148b83dec4a282a750ed4600394c67015bbe292f616c41bc74ed0b658ccaf3681a2a75e453c733a03fa5a15cf20dc4cb9e06a5cf9ed30da088f2d4fd6733b15ee38c33fad33c5f3438b780c338a5083fa713998e838e2883c60c25e0f67046ce3441ad421ac4a1c13a535fb4c801b770887fab3cc281c794a95e6b787968b9facd2c6b057e418b504bd9d2532ef55c94d172c1e9d7e75cc699f66e60e02ee7b143f06f58ab1c82d82f6b53da563e4ea6a71ebe44195cd2eca09df006c0431cb2029077a6cc0e8bfbe4db9d9d18616cb45d5ce402d07217ce5b3c009f8c3201b28594274d841f25529044d1243dc67c97366e3ce7ccb280e7fcaed75dadd3d932f35e8c592cf79e892daf16617c97091b71775e88b577e3cec56e8c15cb2bdb452ee6331213f30db1721b6b90ae7c738e5badb8956f1f277d6870d1e8431b933f3120a1a7edaa31fac99f2c8624611226916412d62e942522d24c983d4245394efec0268d8ce590857de6ce53e91305ee3cd39cc60d1a1965564e35a114d1d316bff2d03ee988f8a0d8a476b5df22f0114f3f8bf434b451a8671e07707cbf729567de4de6ab0fe4be0de7f87da1fcd5cab9d975b2bb9573ab2fc8c62c1086026b7e6dcfd315a7fae2ab3ef9b6674e59a262d4347888286968680a8fcf9c3346c913238d84b9f018b73d93c6454c6c7a37ee12264f2051105d5f8c8bcf454c781dfb09853f17dffc3e9739786888c8e8fb88c4fa933945b05dc2d99e4c4aa614030aeac1fe7dede2eba696a1cea18686888cb0fc49c5a48ff489c169629c3a456a575325fa8446a152fe884279ea315099333444644424d632a7c884090db56b23923932c7b9542c0614f4110d310de55447d4e04a478de850cb508f439808bbe09e9fb6238bd42e179cb63f793a145aa2580c288849e614913f34168b01057d1ec3d753965a667e72480ac9a09896635f963e43b6c7f6d81e18fc85176230786863b1185050bb60108a31461a09f38f1219c5268d84c162b0180c990ec5246ca888060a9a33d6ae060a9ad7850fe68b8592281693b0586c084b18914913a363f8008203b6a1b1d118353330c6d85b00406c5a6cd7ed6ed6ba7df4a54733cd33cd6e0d901e0e9b969a474bb7acdbeac7846baf5e8d76cdb7f656bdf2e2256e078f28834764a91c068c9979511363e8a3116e998e9416a159483b8f1e38a81a0cf1b766a7edc27b3dc4f36b5dd75c4583cdadebf5a7e7d9d3a7964315fe1ab2eae3cf5e81c58a2c161478649c4bf209d29c89c19244bafdb2a2741d70599679ce5104643e3dce3a10d6739cb56ed354721ce736e273b9d5f90fcea7679fa6b9b5dd17b3edbb1e3d0de7b56ba9dbcc4a7530b21e63381df5ba4df358f1c801995b3aea671c11da02bc39e7b657bc3ab7f2e9acb80efe5ac94374cf0ebbacd56e3d0a2127f6b1a8ca949ff9585445e83397ad2cfb0b06e6c5ac69574d7df2655bbf524a4f3990eeafdfd891caf7d6c43458dd7e30355f2594af2ea14499f1b5c624d0db2cb331328f27b76939f4622646cd0c1bcbc9cf935ee7ed7e925476a7bc7329e5951fce5f6c65ec35b7f6760d56d7b2ad6af14583d56334585fdcf0516bb881478f6a933c20242979555152926fc7ac952778c664dba677e4b28f4d49c9c71438da56c518715ef320d96b1d3b76f4961e3fe931768c339d64e5761ce9fc28f5ec55ac982161efdda8d5c16164623982439611608ee36c8c12985a29734f4c667289cf4f9090b54d99dba8f51194def95aa791108165457424129a7110b8e965ce0b8b8a943b836b58434715883452f63831f408f858e4040a9e93319ef4ccfe6919ea442ca1a714e82952cb179c043355286f731f1575f6928b31fa58e484cec725708da94b393939257855aa13c178e3440922183f9c3fa77f31b5552023d2ca1370d861556699703e3369b8ec0ae03c731b1f2d235d2addf898028711c9864c3a47135f92c85fcfbe2dcbeed558419c94525ecfbeeb3eaa80c3eeb32ccb5e482dcb315bce42fa32f280b7b63ee0ac4b89f2a974523bae01ae01268a30a4b3ac80b3240da5913a4abb3697520a4d9491aecdcc482469e27cc051125df2d4dbe7a96fdffd79da4a61f63787528a979e25f1205f3456d12979f00f587e28633d27f5592b95924ad849545e7a272dcd9ca6764d29a9cc9e43404c8f31d185053864b500677e1dd57ef2a9a150a8e83c6ab0215aab9d3479e3a975ca3aea9ffc9489ccd99d58edd2bcadf7e66d61ba8de3baaeabb5ba8df8b5baad6e51aecd89fa22f53c7a737a9c53ae03db15b950f25069a9c5d9a7ea69c364d57ced1733944eed0a3e1dbeb1c3da1b888ca01cfdc4848228ed1b7c08c1214a0f2ce0248b3bd9a23e3db32d9a75d427d2e0cc7352596b9535c8c8677635d8d060b7272fb7f9ad35f3ea35dfed9bde8da6759601617dcdde63d2742d533fda32b5557d7a763d6bd5498455050e635205d2bdf41eb60269b0fd063a054fbfc1519fa13d3bb620e9b2c00eab603c2629d584a3ee0385ce4b263e1625fdfc9d1ca553ceaed6c448c2d3e012d9438b2091b079830f2138f40e1e15b0a1460d0d9bfa33cb1fd615d8a6bdaec1d99a4e7dd2963541463e5ceda4273dca04156384cca10f20ac53e652c887da8d36435b46b6a44f3f6539c441f29967d3bb6151c1d6e5cca489b154d560744dbbe19c524aa957ead5afe3fcfcc24ec894b29b191314cac1a641797b4cbbeecd79fa4c4b6f15cdb4d658bde5d987bb191bb0a3c1e8f4931186d72925208902e56351139a3e94c1bbc13d387ed83ab72955c5d41f70c4e2763845ed17661edd5a6a53dea35dd86b8a13800ef6ecb381abfc1ada1581b01ee756967976afcdf1d2cc1b4ce59afaeea76a10c7d01d60cd0dee7eed9a20f862264637f86266864d8da42c1856b594fab014acb91183c68b991936747b42a5ddc9fb6e5701bc1263c570cc465abc03968e7770f53cbeb0cf4cd9398d80eade47c41b949293dd9eb7f5aa646ab0f2e8e17d3bdad57da3061bbaa50d914963633d8fe621b0c6a12d873787b8dde6069176c5df41c3263669624853a6fabd2cafeb52de8daac1c8e231259198a4944c9249ca5c244524261403ea5877de8dec6930c6d8b1e4edeeee9eef31b1983aa761b1450e0f8f121eceeb4ede8d97a592259fec69b0121165aad75a2b11be5697d5b9163918b35a8c403221b5773747e77af546523daf95c894a97e6f6579b4bbd9d33bcd934a228ce944264dc52ca62f9459f82a99228cea282b7018a17ce7d369547ff9360e79776d336958bcde15962f4699f6952fe66009b4ea3cd66075c9d43a4dd56d3e1d8cf3109ee7f6e4799e976fc41cdee25f4ffbe1bc9bcdef4d3add8bd234ef78384ee3384dfbed5ef7fc5aeb1c672dd5bc3fcd6d66c7f08d5102ee1eae657cc0c518aea5c4e22393583df368b0df5ebdbedd8b0074ac676d6d56a54a95a6cf3293ebf16623dd6b08af06759be556addf93524a339e55e09ffb3e703787403acef6607be79ba793344fc3d8c631c0d5bfd6f4ea767a7deadd9a59477d9abb577d6318d91d23fec431c074ce49b3147a49d41f8d021763f00e4b600edfebb776d7737aa29852cfc321ce9f3ae53f6eca39af1ffe525fbcdde18a3f9caf28cfad77f276452f94d6b3ecde7c1b9499c7d78a6fdf5df96e236007c74a85dc9bc31996629fc92c7515cbd638746a5bc5c2d4c419b1b3acb7deb9746b61da45ddbabff56edbaef671b3cbb42ccbb2cca79f3adf3eef0b25e7d1eb70bee3aecbebdb759ba97d99cd730551e2111f8b9ac0f34d807d2c6242ecc30570281bf125cfc7e80d25ca9050a4cb0e78070f1e11c674d901872c3a135b3aea530f65d3c74f4618a15422efc7a728cf3d8a9ad2f33a07c27aaf3b6559e67954f3d97d616cfaceed173d0ee7b97af23e7977729b9969367d3dcff372ebe43f3cb77ef2dcbae71647484c3a7db1fb709efb8074bfe5fec2bbe4ab6b5f88d3f3d56de6955b57262ec684f4e8a97b94513d46a49d4913693879eec598c5720fc12a83becad54f02352885688a5c18b00babc687428f4d47ad11e0b07ba83766f9ccab582fde4c8de492e3110c39043ff4687473e82b398c432f396421b1587238a30239240d2953a197432c39a4658a04438221c16aadb5d65e828434699a24b5d252f760016b9244143655990251a1068390264d0c1f520cb9671065a8e726f723c27311df3d58c09a906047485192a428f594a5a6d2ae8da969e6b40b069b50e651bbbaf07ed376eeb3113f73d55763d42a818402aacfa8742d3685492b999a010000005314002028140c078422b15038265484553e14800c92a64e725219a95910a31432c61842882100000000044064660201903e38eb3de841ee805adc29813239aff108b46aea00ee5368f5c454e1dbb9f50d57bcacfb0cea2a528930e4ed788a6b787fc9a547d295c13fa05fde8209b1d1b4b31b18c0d2f84e0074d5620afc02960a45834b953129461ec535b9b1ff227ea5b1b1bb0fc451fb139e3d3d7cf9fcc46621af7bcae73b2319fd2bfa5d579c55c560b119920c73ec05e6835f382d47a515a0394627efca95a3a41737868418ae7928a3075bb2fe2c3085ab012453ac01db100ca4d7c8479a082167611a8be607032d8e5d117cbd49a36d1775029e846a01ffa7b7658d360ae4fe2abd2224c09ef5e4da55b4cec642452c8524cf173a9534da093118d68ddc9333db22947833063262a5ed319885111adb9162f426de0ce1f5fef2f23e641991450a0c124102f647988cc78b6105367df783e8c18a5b2bd388dc6dcb96ae186344dd9846bf27d2ef388449c5bbdd88d4e22df253bc5dd8ee7668ccdd1eb90185799ef1f7740e709f224d1857d3d439b181bd49ad035a53a2b3b0e47e95bbd6a191ef2f02865bf808df98a664379f71e3c86eb00025952c91c0b6ac075ff33aad85955ea153f5bc27393c92b17bd315af0b98953d6578109979f36d8c2d1722902b6fbcd6a4a8c6206c1840278c3428f582a622db744a8a1cc590c062f9dbdf440be6240dfccfa2521539a5eb247e5cfe52b864755b016c4ee18bfc8b270dd5cefe90e627f775d64391800cee56576087f178bbf7202ce7d4dbc043c19d2b48b40e1cf07f078620170383aa926ce3f20025e035e8eab2937d6c7904db7b318654666cd318db12d5ff2ad4a7ab686d200eccd16804ad693a20e261966c463c0976c5ae388b48f38678aea05981ae55fdef9bb285ca531f962d8615e67f17ac558a2941a0f65d44575c2abcd72cfdd87151e1590d20d550492a6936104385b72c5f89120e414899551d3258a3c207a6c25a4ee824e15390efb7891f93137dc1121b3e888813f6f4f0b845c4ab4e5e11ee44d2396382101c4483bd9427d76579ae292753cb6220fc66ba96f25c4af9c33927d71056c214a98e4cab3641bea0245629bf827f8dd66c00d51df8ce5b8efbbeb9c46be5816462d20206f32d6724d5d2cd7fd5a142abe5e8d92bcf34df8b3616f3d6f21d40c4787a0837841e8412b6f300332cc9ccbff8f6833e9af4030e7ace7cfc0f2f78892aa31432c0fda978e80ebd95cf2c1dd492526f8b892b39bb70e8d76ed745c660dce61f0fe6513b1496111ca7d9a026293e724083133e1fcbca73bdfb96cfccb32028a01cd60c039638e1ddca8d9097ccd9f3a39c3c81513958f47648acfcbdb14e1c29ddeb727101e17c26cca1980e79595fa783e2cb7acd20a3acffcdc755fc5e16cac8fa2d780f581301f0933c26d4651ac6269920ebd9b4af47437fe14ef793b1fe33a216a4299cd92d70ef5b52707be2b0617d1bc2fa634dfbf489ec23307dfd29b0b0818a810e7255f6f05a58c3ed06780b2f55525f9fa32facb71e8fc95792bbe9f5d78dbfb04e864ef432a7050d8c896ab095a73a27957e85f559ef02516a234d68e6571b36bfdc3d6fa87c70259c4c1ca33af5b637342cebabd875af18040555fd6dc4f7fad8f5e6d6c5a938b94f69d17c87f76b2c9ca4140f314aecd305a28f5b599ada70a21e4b49028075b2040fe9045a15267dbacb7bf50fdffa88351f41172ec77546a6f74d9f4e3e3f8a45d959d04dd9c69004d298aa3de3e5c9544883b16560b28649b2639a1fd96ed15f5e4a5c05f57c5e8a912ace8410673565071e78d5ec2cc4ad536bf3022c4b7927d134c412d18385adebe470c510a352a18d94580ed352d0c2c4f854aa124dcded2f3db54e520e1fd49bd2bc063b05e5e0a48b7235f75ebb6dfc72da5bc115127e3c28e64b7bbdcd95c9dfa72dc6c7c7d253b43f6ef4b1ef3345486c545c5eb6d66f7f81a9d302a5f5f2730b40442f9d943b1138b11757fbb0bf3836cefa40446c5d2437f0040d59cb562949544de36150264e720a9f41b8b795ed73855d61b095c08b3d4194f62879def1961c56b551457c921d693edbe7c373fb39dd03ed54874dcb76a9460056aa0a9410eaabb8e8c79114c106a71b5bee9a6b817cb4f2384609e740a3a1640443a228533f0d28740ae804a4742d97eafd3787e28d1febeac80ac7e84e94b99645a1017a2793d1cc1315991eea22a85bb90e9655549a3e071505acde8f134da212f6032bbdf83a948ddb65869943824b3d7b8ad3d97dd09e1fe67db27632a0b55709335dc225ef195e42689cd145afc89c438ff4f891a421846edaf3264544e2ce14d98be57f08318c7cdc85182118b7262ec99de23f427ae18439596f182cc71602acf8613790191ff8ae078548cc08e38930d238a5b8631047b93bb21f5ccc405a5f32ec76ca8a68b808b1baae45c2ef8757af4111178219b7da8640ead7e71a910af8ce4d154d0092c56180600d202c9bdea24b63fe169c8687b6e4c453ca7ad28cd268d4bba08f946c69b197ae7b34c082bce29ab20091aabdbdfd12b480186cfbf3ae13f1a6914dba65890e221471a8baeccad6aaa1b52800925cc2ba9f43e0792c6c0805838a679eb1281ead0cc88bca21e426f912196dd3640210e1ba90a4e139c26d1df367aba3f7cdc9125ae2c98835df7a5b1a9338fcd281b79f23b846acce9b14bfd864b23bd08a8ebac00b0e98875812c564ed89b9274597bc248cb9622c7f1d060f3e7ea482866bf09c00538e2c2f99fbf15a272e9994e48b39bc0641943ea055b823c30ede930782df48b514d36f7cf13373dedf114aa6e26169d255831e9c1886a73f3a9318fbd60669138ae6991ea7e9e5ee92352de042ec0929b3670a3cf9d8b0e87014f1065c3d5ae2c7a85b86c0e4d1018ad1d30e2a69c7b6fe22ec02d63103e6080efe139d827da87d504c1031f9e6794744e7124a99dd69ce2c524f9c5210baf1de46d238130d9d6fa0c5dd2a050566489dfabcf8e7a31f49c7122ede3eff0d22ef77e5ebab1fa73440300f5a5d2776bbeee6b6556c72c62da6ad3db5bbe97ea1591a13aad729da07506e8c7b3ac47d6bfc22aa650c019641887bd26be4352bcd163b9489ec5c2afa56f4fd5d47a8957375de08a776aa55aaea0149de260e3d3f758679169f7a1660345fa4011fd8a8a224219fba8702a1a36b9cfbd49af5b0cdb1797f4a51c97f25854c75cada1a46bd7dea0f904c9af2735dd6c89067ac688c39a8309a304949b44530ec7080004c4a1b79f5fac1c5e1a86291d578e38cc7e0d21afda0d385693baa44d32cecd50ce34b5b45ece47220b55c577071c037c1d477922208cd3bb51e99eb02f3f1c9106135a670db491b17431ac0425b243a9491e24d2cf1f280e9319d37fa3ac59e56980bf8824f6866b15fee8c79d9eff338d83f0ca930b75e2612728b937dd1fdc54c769c27db36fe13b2b75ce034546b3a4d15c4effb0f057920f801836af97181d27ff46cea3cf6d4a79e9b886ba37240bed7a2b6cedddbc46c55f40e2813848f9d6307f5a5dc06d91a901211492fd76836e620caad77d188aa030a1a48a6509444c9a009d3bee5a2c8f487149890deb894a3250cba83e712afd090b22b0e4276a00c08a98a65563339c8ecd4fe49267bfbbe23a6f3acd030a7a7676f429f07525c0dc4ba6cf709175215b3754831d8a010c78821239023c8532429b047472a35c468f70026a932e77bf51f44034cc4f02da70966be349ea8ac74d142aa9c3e871b7f4a5b20a528c071461e747afeb39b51ad8945f7249e3955065dd1cec8f7082d53d17988abf76522b2d32d121757cc4a72ef8b7eaf417b363d911163705ace41e79dca7f3be205ff86346a95431ec4e708188443e8edb3a2549f6ce625856cfa995da85c7f45e8c8d9c12ded98e76f9c3a0681b8fae6256e2b2013779fcd9b97e2a785cdc1269f47a19f5621b16c69f5091b77c3e236edd46ad3178840c2202dea1accceb9d0af46cc8bbaa05079c15213f8417cc779c130ec11f98d2f2b8d32c1638462dcbe498b75da2cd627a04048c5e14756b2680dca2d76e7865d0b774e6623f4a654a6b369260ad8b72ddf1398dbb6e6c3f2cf841342d933bf2e98e4982e05293ba5e78c0e5e49940433cab9394e3a44f9230113e8d5cdf23262b6c5a10675ac26ab014301e20e990b618df6d0a04da623036e1da8c0ee677a5b2b904d04bfaa51f8950b7240d2d017712d651a69318880200dbb3822036d4f84c574965a91ab2a64a23aa22de87e8fec5b29b7fe22dad4207fd1c1a80feb55e078204e1209035aa2fa8fdd28d7f33423019654cf338594f746ada6d3533f82535c204d6e95954e870da628dd7146d029cb8473211e40fdb62d4f3fd824431be5440227c2f7cb0e015e2e10d16cd9b8eb6f23025fae1d71f25b70cb36a4d2c64192646e3152c85ec2a08cf62c6b7aee33cbde6dff633605c960347574e73776396d88bc84855253776d802812d21c669f977cd93430dd4640aee7e7d6f85536a7fb478ad3b23c42d7c5496cfcdf02f8f7515aa07204cb92e86ad02468fb3878826336b3064725aed1cf71209098b05e22c418661c1479ca243d2fc672c70b943af5012d709d60ab9310b9a4630ae8dce9c019071cc38957d82b40f8f89eaf87ae8ebfdde3c89a43d408a06706da48020680a02e792117e7df646b261a987b98c9a6f05403b33d6390728d84398abf925e8ca780a88a88cbd02dd3ec3be5d522b488a6b7cb7331667202e6b8fff75fc81024dce4a8a116e29a2570ce80897e28a3adeacb85821406a0f7928b2dc10704a6e021c0676aed92d89cdc065b4bb60146cdba5a44a769c44aa1ec3d87f6b961580f40e14cce69664b9612227b983210df05daa481d22138b7401bdca5adaa610de5597ccbf1d35443bd225b30913568abf490d455b34ed66d11ea0f1ac73787a45b206f61064297841b4b9c562703773051fdf1805c20acc9278b95a0cbd8680462a1bf762fc99995f17856d90bc83448c5270c687575abf8eecd83d25c3e1d1e9fb7802b660973561cf65c93c39314345db02cd9075239dbe6076c3056e640dc3a41f1c6d96d65deb9895a237687f54820d52b679c792c148cf03c2900f75c05a996b5b8487b9e95e02134bd37ad339b3f71fd5fadd0371d24932287e964e366c46219edd54a4f1df60436f51d5cd824325a2c4e95cf46681c4f496ba8c1ee8b8dfcf658cf34bea02e9c83d9edcaac44bda1ae62ad07d72a36a7b9864e8d0cf92c72803a2aa8207dbcbcb8f7a6e54a2f2d3e7d41e6ecbd90ed565c764e5d9d1695372906751e4ad1923f762cfe0ea5aa4b2c638517a3779efa0285e35700290c98b11c253b6ae85de98f19a074e62ccb1d58b95f7981a2d7dad2ac739c12c5bd2ee9f2dd8047d1996c0e95e7291713bc7d11f9dc20df43896b8ba7ba8a6adbf37735db4273ab40ecebe0d46cb6a457bb82a5c26477d4c86887593cb7578f2d27c1550faaae10bdf206446b1f0566e3b1ca9ffc06e7b7218efbbb15d784afa25ea8e634bc6835209d71735b778da5592e1770be931e5c5ac4ee7e72f90f65573019c48212a10a97eb7fc398d616a4f898c0e6e6c860d95d8cc86cd24d860b847e11602d1d04738c7961a85d34b5c93ec0fb2cff5dd9e8a188a40a6748659cd4c17dcd5a54c92b23677afc295be85d604cfeb4dbd392689e2ec8424e50f0177a1f62f00e42147a78f0a1571eeac31817fd6e5471e04c0fa9973a335d82aa45926c4d4f8c5481fed060312735b7b3aa00e32340453fa57c69c77c175203e836814f9234470ce570d0efff966ba21e5372d694c5d89e7dcc0b4ae6633dc3eb1f57f414b862ba4900dcc6170aae1d38c447f51882c0263db00d5579fb0c0814380a15f403b90b98c5ee30e2bfa896be45fb5d01c5efcc42bd89ab7622492338936a281970bb1d4740a7f069e24c11d6ff089becb221a2ff6c904bc289a3ae5dc438085259ddfd35a4f20c260db05f561c50e2fa3384e6261c0e45ed7c5219c585facdc3cd3028503ee85d13761a8ec9faa64180e530cc989833a016d0b786633ef29647212894d4979a9c5b736bf57c0e8105806debf20a31856463d310af5f3f349bbd3efab0647d386b5592793f62985d231b33ed5008b480bf7588629ffbb57edc3ffd1cd8480d7672993bc9d0316235a6a8d20a67f433ea9bb771b6a2d1980f77f465bf30887f00954a564e580c0e6b9cfd70737d680c186e57487aedc35f72b5bc00c97fb491efcdf5c17fc16cc33e9f4d38dee7d26342f51d33702b00903cfc4932b19f8d2884af85b3e58fd298ea59094a90a31d9cdff14b298e6434638aff029def133714e0bd4e2766669dd4274abf3826c50c9fd4ffb6936d4248a19af89ef2056ea7d01514d16c7ea0537c996e269e79548ae0dab245f11025a8d1069096915f966614bbb796b23d715c04da9160ba217d19e45c729443a6dc330a05e32878cdc000ff6a82ebcca021084c56069f9abf2ad7861455091ed0ce27d47c6599b3bdc992124e57299a4fcba04f3cbe6474b6c132bffc8d33238147a820b638ec08ceb4daae08b81ac302b3ea0ab8a202debbe46e0a9857243041d95df293e209666acf00d20cacae1cca2cd8e96045d98a3703d0cee8cc3433c87f5970f20376fb8620db21b76fbeae189570bd5c34e1d8da8848d177451033e324a8909e5de1950cef2b9d18030b57229891caf3517a45fdccaf43ccf025f724054e34045e90b7066fe24de80cde5fb77fa179a020394575b325274d6d55dc79043ec1656111eba75dc6029be9bcb6d3148115b72999077bf02e13fb4bd7ecf20cc97107cae742582337f35fdb74d61efd0bd1e5d82b69f2b274a027fc2ddda40914314b0ae803ba5270e6d034089a1ee2f80ba5dcd7d0c8049a5789eaef22d563be404499daaf98a721683ec92aa377c01951632c7f491a0aa941e4df20f92c01e6917e3279cc276ec03438fa0be22fe3383258202c751fd2643d55c106b3b06c8a3725d0ac955ea4b223e05fab0bbb51d9d5910bd5248c476736860d11114ea2acf3258a4ac39d3e548ad58d41cc0c237ef6d5524d5b728513d29fae1b79f46c2ac8f7ee0816e18386ca748fc032e8f6f006f0138004bfc07897a6325903b6497e48cfcb360ea02fd3ad32f00655812d5bf20051e8954cba1ce1086bb450880a4193aa2e53b296fe4699269c19df1d255d7fc99b0cdac5820bc5fda4214c44c062493047debf217ea25811ad9b5de12274c85e7c4900594bd82f49f0ddddc4e79abe68047e5cb13f286b3e8086208de8335c785e3d9c20194275f88258a7c202676d6c89f1e3241eedec0df147a9bcea1d2c39d1a376b3e79462bc59c7fb7e501d83d7f22436c9247ec506a61c7c912c51a8f6a282f0d995516e7c0195bec8429968655a7d86ec5b4825c01c70dbacc40e75f89a77d34330e5432a0bb70193106925a0ee617c838aa9ea5cc5826da6052d0261cba0c724d8dee16dc0a25f68ff3c50ac7e55c2d8c16eca86ba75c0d6acb6d733ce3591a04fc40d5a87038f770e9ef70867e1c8aea175f8198a93238bca4929f6ab887fad563ac50a2cbd1b53e46ddb0283ee0409a57e62347ed16472f21beff3726dfba25d1003839704c343ed46af7c362a3777e9802dbec426e0edd7a181351cde384ef375905d15e0c0292261aeeeb7d3e67321ecf79169c9fee1aecc4d5d72d53e106c0734cada0591b42b103b012d72d82df4b24351b2f05538dc87a2543b50d344e93501ad43ef5097bab87ff2f6688f953e9ce6166435f88bd3d98de84f382751952ca2a18610c4725909eab59f19a4349d2f1077a944445ad7e22bae368f069e4e03c7bdfcd521f8b2d30f613e03214811ac9001358632f44b655a3a1d17c1bd2883b89f0fc978c02f0216cba9e70ce2bda4bffb117c43eba19f9872f3773c17d715ec7b7048cd5754277887d1b10d942af51a7e39d589fa690533663665efaf4d039ad86a687fc211e16c49ef0653e9c01ef0aeaeda5e0b96af440c9dbf4a764ab5768deb69599e811ac58d38662b0e305626c1dbd3e82562f7fdd1ef6bf5aa7b62eb9c9cfb2447265951770cef3c638ef63c68e5163715fbf11cc730792ae34f638553744e1734a86ec1ef1952591c383805086a51207f444a91aa1a83ff4221e93bc9599b90e7d771f994e0d566e411a44e707fcb6b9e8e0a56df3ab4242289442369817e6b7d8ee82e963ab41bcdde16761bdaba60817701f38112c7bfc8fc354b30a3f9ae2bd1d1a16fa59a560b915930aba19f736996ac079f08e294b95022280521e6a0e4999067f2a9083e5973e7546c1414b65b1836ee71e5bac7e6fb51fa5d2ca12114ee93cc8c1c5134226a4fb89f993d4cdb7c3b865e19c88e7d8d710492334b1cccdd0804784aecb7cb621b7c77e3818e670132923eaf6d735be3e1fa3a12d1a42708bb84c0e8251db1749eb5a98871795b78608fca6b908c384362b2c457cfe41f40990075938ac53c0a90bb2688246ea7347be3dc17a147d3928d4e05c6f64dbf0144b4885d6bc9000df61949dc389b315d527b7065d33e4c5035ebca4b20fca91c1a5811968d3dd93c758deb1da039ad3fe4742d65fab3b3f9e3ed61bad9148ecb9abb7f14386830e6052e240113aa01e69b7c1e5a77f857252eaf9de860969945e86b66e5c1f2b25abe5ecd3d81fec3e71032a2afb34cccf5f6a88aa4c67df0d8475cd3af49f2008edada2c1b07e4dec3464854aa8fb5b4540ded46b634bf1bdaa69cd8e6c9a1ffd0e2c21d50977e75ab83baa379910e30a76d833aad38a3571a4374ff6e2b895379a01879e99195696caa14087e2060e04eef40b8bd72022d705f171f513ffb46d31aad4b45cb78ad4368ba83031147a9c5717d09a9a4456c07c11543a3d989376d3cb9e5ff4576aa110aeb28524fe5a27334328dc3e04f3e322a20ec8b3856db81c4a2a7ceed2773f4468b54a98be4e350e578367935a628927f03b272dc565191325cd2e24f45b40a2d43e539412b937880f0f4fdd5cbfc39fb51a960e3ce0dc58428c455a6799f1d7b2e771eb5e1a58a9d8906212a685ceb17ba216fc671f39f82fa4accc98a08f5d269506016c9ce02c73ea86108b140c4d86c063bc458bf99becd8a00f1fe4810458d26e3bb301c9bee3069e5779e8e2cde37fdee0f456a87d497c4174ddf0daf7c2599cc21d88fcdc99c8af725649bd8da315d22cbe6077583c4f6e4ef5600901ab106b4996b3fd3abb023fc5d960db6705d347124608bf2ca5e792f3c820ee7e6d5d47d10a171ecdc1ae200c2024ad581684ba999094edf385dc1a0fc9730184e0c8bc0e571c0554f88403128fd876a25c7af82a81acf837c01cbc7c0d09c0926372d5cc68fae1e36ecf90288b000fc48cf096ed170a86d2cd5914cbfabab6085f217218aa5e182f13d4d81e8a20590de96521df74081abdd675ac0023b0755a7334d942513f549b37fb8d3e4d0f2cd613575e5dc6c95de63f8423eecc744f6751638d577359b40ef1790638c7c19517f03c1a57c3a86f5a7b4d9a315daba8aac758f707c0e472d7a0ebc0787e5fb098ee63001d91de1cac35de41b9718fbd2ba7c5ba6df40cd81bd6418f9ec28d275ef32efdb55949444d9eed9c3310327c520ffb04c58bea517fb33b058b2078ac69b7c828ca122566c15f0e51f4dc1dba2d014e0793543f95f18ba5a256e4c0eec81ec5a4f5c5f0419c658dfbd898d42ba1c0854e059f778b4e32b20489dba8ec268eb0ffe9f5195cc308ba917eb1269917ffc8a5dca8ebfe2bae2c6770dd0904a2a12079a39d2aff55f58428496b0112afa6c6370d02e3c56b82adf57f7efb656b2fe3b9b7359c0a52061bff34c8e3c657b0d2278cc5e929858ba05f8f50cf5b1be15584124319491f81a4709a95057c6f260659ac175d47f8b2908cc44d2f6dd841e16ebe4e8969f187d956fc0321098662da82e8a3449c3b52debfd5718704e2becc8ce0067104a86520a0d4e15234a25d95a32fce03e0203630e49569e43cb6e75c1c56ed19e7215363072dab6e27ba3fefa409090c07990d332325dc16ab9610d9881326432dcd9d2da44978a5e9b75db46806ee2581978e3037e2ddf574452543ff541d8f8351bb784a53d4004cd2b702c254c93c014e6fb813d4cd1c8e52e88fea8a0f02f8505f37c5aeebfd4341315b627ea55d2e7359f97172495c9efc4528bfd056cc353698f82ab26b9804964b1312de60632bfe26eeed6bb08c9445e0123adc6e381e50c8b72be779746cba3710a98101d7260ac981c53fd1e7897749ac7521ac5586679e97270fb20dc75b5f5858a0f8c62ed82ffcff3c9d55f4dc2cac59684e60d09cb6f12971647765fdc8c0b79b13f446088298b8444915497486d7604c30771af05de1128ad89a008763fbaed40c53cbc93fd64f0a293a39c02bb9e26f740a20ee7a4f1538029f9d28dbbf2e9a4cee8a0576333a9be2efc284b189fe0ec3185792aed3d5bbae3004c4a8d8acea8a5da12a544ac0149d310e72df4cf9cfdb83b70cf27485a05750a430b8722d06089d8f88ecadce181ae9e20bd695f4f50ed74d45ba8f7e3cb4189912e9b1b40fabb064969b06d495d148572536ad678e8ae1138939a375b6b40ced16300ed497b5e5d3f766e547cae9c7ee97c1fb4aedd63e2ff3ac1c5f7b8f6042b13f9567972d38ec4ecbbad75f1117b0f915d56a085404d0e369b660bc9fd278673323e50d4650c109d2ca2da8b28a00ce03a78cdc121807bf2e38c5cdde0b418d76689802d23d789c4cd0ff819554c969b2f257261da6cdfbef7c1dc4c0883b707fb63f024a20592bf67f866d3f949d79de4ed9dda990acd60aebd473e3f46938c3014b6dbae91bf6ee02019b09e29c0c8b876d3fe67aaa700f5824760aab8b82dd3deca1cd9318a65463630fb1fbf542e72468a63181eb5523a7cabf2ecf75dd8856c3d41023d7e1f6146fba7c5993d0c92024b8b7cf1b1f53f0316a955c68bea6928636d32073a7d8a4e3e9d29a32263f1b3bcedff0724f457cb36f861f67228102663cb34686470207fb09a77a13e593dbf8622ca3c6f00e95fff8071a6d7ac400671ce24dffe66ed9dbaa33a914281833e4904fa99123f068522eb4050fc9bb83cd397b9629ccc45d5fc83245f10bc9b431c1fcda4f52d8c6dbe7f8432c39cc7ba4c7555ad11b43c613653790ccd3529ca4964900891759fd8392098428753ce1a80ccbcf826fd88f58591c4c7f46e848823b2d3ddb504243700ac6b3daa0d13f02b0864f548af43c1453671a1f8f9cbd2be96d50bcd36db9bd0d62746f0fd2f8e96bf223bae2d2918f4c05586b916387814dbe0246f86df18fa87a1713dcb60ef2518952c7082d80809109364a82481c7b0fc12e054886e1d2426c63c12079a01083cceafd083de16f9da68ea6c1a2e4050494fc2fe4380499332047758cd6356513b4b3d88bc6a5f83ad7aec5b9239df0ac04f92ac368988dae5d295164dc1869a11d209278d2fd3e7e585e15fd5372c59c36d2133c4c29f1879b384747effcd32df8db46ce852e45892455b55ffac2ee519666bf316b1bda30bf5a836ed4c87388fccf4ea1dd310a8c0627143b7f10f3f0a93177d03b79950d6b42138a68e22a72e6e451d9e0694c45d99780b9b2d74ffa3d18a362a803b4c920532bb2afc2f1fd8c8ca8f3d29827040ab453c8202b09940869c01182bc3c3d5032c050c15955b2e1c06a424ff9ceca9f6eb30d27009e51930a8c958771f541f2fa196836b69c871d3bb4a8f95d21337ba39786ca75a39da62a512a226edd2e895053cb6ca18d7dfc2eca7278ac1088b3086a3b5a66ac120c786eced96877a512479c2c3ede253853fb8433c5e7bc91b495322889c6852b484cb2dee10f2304d3be15e96cf99821b8c9ee9f2ed43708af47238f7812d66f672ce85ac4b1eb256b4da5abafd90a59dc87eb5da4c3a5118649a24343f83472f7af1afc0b62a64bb650e22034a696340b234bdc24c5840bf85f04a21d8b89ad151e4bbbfd4ba8cb15ec7c91e203d89cda6a57e4a00fc1a441523bdb4a9ef087f6d8a8c30e8ba0ff33194ddf446b7118c5618eabe57a2801b8040bccc9d3589de4b4ec320a8932e04b69a8a6d316bf7df251c356d7ecec115b6f97888358b5985516f800a9d2ab2cfe078f1d5514ef5f2243b5beb71455d5d9cbec03c3edd5f3d319d05e43e46b9ae75687b51179e326e4cbf9c6560328a3e24d573c1ed9c9e006a117c258889e98458f44205678a3d29ba242248817f794a9141268930b6798107bf759ce47aac9f3af05a358a31b03464e556dfc21949a0dd2939040e853f0ab9039e5fdf17fdbe4546b58df09712571901cf552d04df4d8093be27003fa00daaef74cd7fbbce66a5b626d050f999c1880a4fb42ef703474337bace2b5f1e404c04ea18fc2f71a7ca935b04f17d2d45241052a49505a33a802b44dffa5f4b12d46c8c52c358203b2d71d7868dc97c5c0123224f7f2d63e24a3c4c22a1335e5d2dbfa445835eb8c3e4422f5a2116c5d90cce21365057c1d8e58ea10ac57679308d9342b613ec1e14e00ecc8d5dc0855f377cb22687d858cef947df73d516ba702d1dc813ccd2175004238cd9615760e757213f94dff00751c98fe14ba26f3982dfa78dc2941d05e2c46546f12cef5c793031b02d4cf042752cbba659381c14e3de475ad90dcfcebb0d28b68a284455c4501851d0e486bb59addfb2a3cee89e6d611270bcd49fdd66293f16d168995d1c4bf83ab0a838bef3517122df71613e96b12210d1d2eb1a4a915b29a5203634e8ee63c16c07d9e7d1b60fc6949309c241682b03d79fe626413727ada262fefab412ee3f4162c8905e0d6018bdb7442afe4c9a63c4b2e4761138a63dcd8e164b709948a525a8b7b4b2a4a9b6692c1b4e63d7584bd0f6b5754d107df8404f3f357228e86cd77c7a96c5a117969026f5f4b91b0919c3a276759956f54fd8ee8919914cdb6ae2919b02cb6b7525d57d1c9df44afdbd8cdd1cbaeaf9349b222c4c7d2b35d869bd54c7cf6c431488eb852ee24eee7436bcedc0d58ee927dc361b2d694c86f93cd124d1af002e4e767b203c30c52af7ec682560fd9c7b1b61eaca2ec2ecd33384ff759e285474538babe673767a36fc85ce44687962439ce2af1c751254360ee422950e95a7099db855edab2a73ec4f3696f98c5bb09edfba12cb36dd5673d5f79bef34c24853f8a49f31896dc9e665308454996ebbb0a5881a4a7ee01c4d71e418664cf8c2ea5eac24195bd04933138d9201b4d4e378859cea80f7886c6cbdcc69c154e90f494cfb7f63423a5027acc7101eaa0f66cdc8ebfb70222efce8816b8d30311ec6ee9252bb175c11da1379354b37a106178839f9cd9e77c4008b811641694ebfa38b39c77927d010c4c184c5a5c3533761195ceb8f2eca99183ec4908e70670558ab4dfa2116f3883ae3c845b9c8dc4fe1d46dc18460cef89e91de09840900a98434aa31696fa3c488a76d0d3eb4bbf1c2a8bb96087d84f7beaae86af999dad6dd9a631320a87ff9048532e45018bc6caa561f0fe6070008137d2e9912ceed4da6da661dc86a09f9bce8cdd2688b892d311cfbce3cb455733c105e5f42b86f8aeb546bba3fb3883acec997c03906668d05d99eb1d00724045a690136c0f995140837bdddcdd303393055c2dd36b82b3ebd374e40c3d02005c8c077cf489534b5e5cee1b6b86d059518ef09b9bf29caa5df4150a7701951d55349cec0f6662951326c2d583074b1e96e2f86fa77cf6a2835dcc02fa648919d398e8fbb6df2c6cca1eb3e5eda0b650ac6abdd8e8bb6a03cf459e53ba38b807bfc5d8c435ba1a5d8a5b5e9e82cfe77b275e7df3e35ba545ad44839d2180136a5b3f3273c494b96aeb28b245e3154d9ec80fd0873169d6b675e354c85483b42bc13d947839e505d82d134fc0c4d3098d1c98f84acc4b4724b8a0186cb0537548fc6312ca16a21037d6139c8d051cc149641ebabd8272ccfe83b31095eb5c33ad23f4cc70daf9e2ae1b1fc6f060fd316c323f9bdcf7f1992e2615081ef832694c8961b8d31bcd4eea5312d849974e99dfc5c9a7f2a875fe8ad706fe18a938ac51b78be09896419e9f27f4264c877fc90e619633673b75cca253c268f3df8be16a70d2f6d099a4e19f3feede03892d18350e3716e2bb1eb144b9f432b27ba0c305cbfecfedaaeb2aa3df2ebfd7610239a07ec2a3c7b0578a10713842c9674ba3111cac0428206e62020ac3cd1d5df8fc873cb0ef7adcbd5551f8d93a788628f49f573e8eb78bfd4fdea2a2bca5e2976d20af7b01fde58b577db83f6fb38be3ea6408118ccdc420d76f6ceeeddde4058ad0b5f73efee007af8963129ef37ea84bb97b64d3e9bf504ceea4412dc3de6c73fd05f5bae1946ed7522e874c45ee7d306ce51e08a04e8501eff2d70132154176bed0ed9da1f1d523274918671582b5860b8c33cad6adc2d24cf4aea12ae7a2d7010d695bf7de7323dbddfa8a3bf4b5c759f07c7afe5321f31a8858b598e38c382b9ec48b4bb19a8a0bebd32b648a4bb5818505c2066140c4cc9e8818540ccab538945b8404c0360d6dab78cee409482295b801aefc4d8a431952551ddc124a658301bb61079e6e6de45ba567628705232dc84b68d2bacf44ba01f4ee2a412a05097408706636cda827e32c6ed29b6cfd97132e5ab76ef32577649b1f8a1ac2365f2d217c758de4117c4d5ef249f1b8dbf2f72f4c637ed1661cbc6862e5a8981b9c8e6ed27e86070248eee6a25d4663074ad0bda0e76a1c468910e11256200814a2321569ca2e7924325249c43250cd2782887ca20fb49bf50c62eb74a8ea48041d981788db8fcb6ac88ab11a7bb1f0aa07ebca07ac603e3bb17502d532682b92dc306cef387f2c8470bf93c14eb1d73e011a25136845cf8592d2a4623af13e004eb2a017c2e447858b519fabdad879914441a0f8bd37f339d20177e9d07354c27777e46741d8c53daa6ba0cd57f39bce8f891b598c4133f6b2b06b8999b86334152e94d5d42f97e9e09521c24e6fb74d5f89d4b85b08ad91ba91408c3443e9cb05389f1ea6ba46438b76f17233c0d0a6e5772456d5ff44f9aedd086a4bca27c6717cad86f019e5a51c587792ab9b2815546f81ee9a60e39357de46b9519d9cd4e6e7094a0255aa13d407722202e9fbab66cc3844faefef4622913528e1b204921d14b5a5fe85c14fa130763b73351fd2ca8ebd8674628992be6b03c217ab0a5c1904f66608aace9628728f24ac25ab863d869bfef3f0ffd928cefef82948a01058b3888a51108308ca6812d053b4a6345194288b8bc3b473003e75b0561bdbef461491c696d4cf92e89b5692c066204a34062e92932c8ae8ce0122f6874033a4d620ede3ed8c133ee7902b4fb00c9914ef42ceafd0dacc98ab9aaf50695d053d6b9d989d81a2aeeea49de11abbdec401ad8551a8e9d7b9c8c474eee5c261781c94aee458372a439300ccef50808a598d960ef1d3a7f449d3c46fb55f6c22aa49d9577abf0ce5def07f4f08588cb749ec24450a3453b7a73e9ec8cfd53ba8453daef4970d698dfffb49c8984e3a20a93386edeb05168030bf01c97a46cbcc4e217d164d27eee6445e01df8e2a64537e1e3396ac25a1da9634b81059c70882d50f0cd3282221022fb85b87012aed6acb5053d914c7d5bf079b67fd146c93e33f5dfb760aaefb95db728aaf220bd0f50db99cb8d6f140bfdd983cb22cbe5c7a2024c2d91088347a35aaf7345f2eba3b767d00fef22eaa103181b39ca45c101fbd2c6c9bb2a219a9ddd104fe4a46df44c96e7c2fba80294718dcfabf682c1336aac7bbc7142bc7159a6ef068abd85c032789642307c8327ecccaca471a40112029f7394c09701a68395118cc2f803cfadc4cd487d49c475f90a4dd32ac0cbecb3e820de2492df9bf789026355771cc202cac66029c9aea716b98ea492600e1893bffc5e3034e327482171f7ac36cc234c6bb4795552f18214f95c9c1d8612532573ed2c072786517cee8b3f7fe6980b30b917d32382411898f2e80caddadc72fbde6cdd3695dee7637c5f376740190b168bab6ba0121d59ceec09b47d3123be4ac773ef4926d4ca89782b00a7da6345df7553ecbcf031a72ec3f6de42111b7406036de0013f0968adb3504d2ce960f52884ee29dca7630d2fc13e3ffb95ee65b2d61c4dc9cb86eb89913aee8a3783e1f270333ad1ea07dc1b02a0c7a997c9456871320d4a985f81c22071e30049c8c52ab11c305c5bf52f97e2c7256afa0a0f7a1db4ee7d684bf82380d5b7f0dfbcb794d545e23e661c18bec6bf4b5718e89c1e67324430e9ec73fad09279d47b668ed1f21aa1428307843125390277c3b36a43b4d1b4d4d201aa7b5e0b059455e7548052243c662ab1c8fa630356462395f473717432d2c79d1891c3ff9260a2d0069976da555ec620e2900479ce55f29fac45ea06e658389dab3e685ac41b513c0c88b091f24d1956816eefc4d14daf23d5de22d5def41553ee224d29f292de124851729796f4aa37ea6fc5748ae2efa622f51ebbdefa4f49006f10248d05525beb0b3fd05e04c608782e16dbc1c2a2c2383e33c29dc214ccfc9e1bdfc153e5e1369c13c94e3d33ae7aebe02f687cf56be5c0bc81f95df89418d21ba9838b842a5a24736ae892252f0784d4e6f2b83dfe65d4575784fe15586434f8b31852308157369e812e3981776fa745140b12ae1a60155c662fa144d75392b139fa6d46ae16874bfcf0827a279e9499778bfb05c368c7b9384e9b7c7f6972860a34f98272d1804e19ca8b75a4005a0d3f2fec8be19697e7b5bffc0128006ff39a87593befae335a987034f4d55765ce8790672f8ce0fbed078de18c57712027e902e7173337c4564bdd27adbf5105aec57715faaf702cc655d079647a2731fbefa8cbcf039743344747177247afbea54a4f367c58a6e4d6fda233c663582d8e5f9a939091d6307ef889cac7175a1dfdb949a6eb87b8defad0162dbc900b0d04fdcc29f78b124c4ad5433c89a578c973a032307ee59a5a1773e24a545802401adcc64a18beb187b7267710bb2b91127bc1de5f465e370a7e66c2e411657565b7049a72bae06357cf4b880412b5f8c3c5b56ab735a1a7f3d30af730e502f6afcf2e990acff136ece9fcf40de3bbfe2e01a89e95f14f1efc40a4f04a8ef4bf127249202b5ef741183975ed2137d6a7b72863609d80f2eb6e1b6ebb9beacdbf0c1fbc7de317d729d767f1a6e7c42f01255ea39b48e94d11cd527547a9005d7c00f64b9c4daa9bdcf96a46512f6f28845359b4a6f2f47c29ec9df132a584d8d6f4540531b8ac1cf6c40db39eb715bc46328b90c1a2f63b53a7dbc8335158c525486c05778d277097863aee0e00327bf5c2b7539182beabe4afaa8ece8dbc265a5f1f15cf5d121868af3a648e6f260cf7aecd73097d21442d25cd348003ae6bf3b0507301c3dd3b7969ae053938be1db8fc80eefeaa33f6516851f9a6c6542d82375c3e11078ebd6cc85104279680c27c70a05c467649ba41f4a569d46dad683cfffcc28aaff5d0fa1dc6d29d7f9aac33b0b986d5db8b05909935a98a52db43c2b942e093d983302edf0b71742b8481719c260771ba365f563263e74231e3e28d2753a8f207818742ed5c39c5e73da2f082fcf4529cab5e4fc2a33e2390f4d2d4fac22fe80f0c2f44902b3924fefe6be2f260a96756daa5e99d6167c919052c9586fdd74d654070bf2df71114cef74fe41b092d7dd74af0a2570b1ad6459433e700c2c436b5ba8f044bf55704889aed445ae162ab7257d4b48c9ad16024ef26e4e229aa273c89a6519ce204d68eaaa826cfcdee10fcd6dc194089e99967120a7e5cbe842a990764a9a6bf0180fe479470e244fec32b096cf78fefc5cbedf4c293263d630f5995e12568bb603a4a70df9279b8ebb1c54cc414e23006c5325cd461632d25a88d62b259f7488f30670a4f64edf75f05b33bf672dee11041f08d6313c4052e9f930bb83bb211401e9a6adb019a436cdcac837ea95503246ef3fb8250218ed30e140ac9503976e0a89470a34a775401c1a3742804eaa3f882dbc6d42bfea95d0170ec799531793adba165872859b131b5fadbb03023c3d31e36603403557328453ccae9e7e27793422be638c536eeaa7f3b2fff30e82d5a7e1f8e2ce0e2735ce4dfb4335bf8deb91f526e72c4a9a4106ca8a4ff67dcc557c40fd0bd90df45da5bb767a9c366bf68b28efd570fec9e513b2098e0ce67b7f8cdb67ea6079a528c2a128f723c323d378400aa0a58000f111e5022cdcbb0f4df41b2ac6932c213288aa7cc83b0ad91e2e22780b20d472c544d638a22464c8bed5bd15cf255ad164643291e7809e71d09ea905893768806b5b847a6991b6bbfd20f37a4ea1aa9caa80379b49f8e8307fca10efcec6b940f2722b881b05770c335ff3d5cc06861ae9f93d67f7e4ee54cde185d2ecc9bcdc0b38ffcdfd1fae94d76987abd7f3929c7d416d5a32f60bc15f46f2dd5346f3844e66fc06594c55bf5a22f38c9b14e6c6a73fb9362713397f31b2eefe9d29d730cc5310004a9daa7385c69ada6796b4afe986228ab36be87cdfc0bb6f28cadde4420d6e00f54216bc6098572fec0ec8e8f2ffb05e8d39be7f9f90af0be562bd755f275123149b5d4d64c7dd5ebe0d6f16faf0bbcae9e5aa158797eb21168330cedba0d978a8152e9840eb4fa58855973265f94f35e37fb4203ea73557d5f8f5c17b2497aaa26d3794ec9fd03cb0a183b1ddb6519b673ff795ab283e09da99c7c33ca792a40ca13f3f82c28415adc9a1df5aba35067ea1147250e9d5642a7868f7b3b66e629016f4e3b6a95e91ce67e46d900dfe032f1d6c18dc55ed8258b7397d1b05a3b40b13550c328e4223c5a8da0669bb42b85319c08f45eda1c8cd449afa4a24b72381f13d1b9801e7521ecfeaafc66513b0d1d7b407447deaab0714a9cc7eeb81e801722b1de891d0cb00d2718d89a27989105fe19938e88f55b8f8d5d12fa1524dfb380aad489dbad325a7e7c0af5c79e424edc90f8263e1e821dbd208544e4a6c5ce65ac66ab457e42e8d74598bf208780b0043b540d0c6aacd0a39e532d95b7aaf35967cb372dad63b499b8163a518b6ac97e5051f7a4f2307c4d4dc0588358c90cfbe819754dcee96866a6455180bb1f81c0c1b4bf46977f182ec7cc66a6dc58f6d65d4ca4f043c47335ebe9d91359656d3a697d4350cbdc0e65533b901be87b6d18c67b3c99f192e574cb3e44d6a9fcfca7fdca1151bafb68bd86bb5b32ce9f201c2474eeba221fea82ed544dc628dc4db2df93057aaa863cd32b9f15b75aeb246a560d199eca647e69cbe73b4159832c33c210a5c2c776b96a4290b0755ac4079bbb4e14aceb5df64c0cd6078cdff2c0b9e50966ac032bf31144a99e574be358658c73eb520656d9efac08081f5b535114819597659432c87c761762e44c29e53f58cdc0d5cd75b99a2dcefe1ebeea660c43ce24cde9128b60970ad2e1fe9897c72c9765b3755d18e3923dd36a3efb2e21e95b67463cc68f99169b0428b4b944c8d400c209cba66b998ecac651488f1f2ecb780800244463ac719f60f9ee22617c11bb847797d48b1845dd285f8bbb965a1f8d48221bc6abbca41f1358d4f10f10df5414428820b48baf75df01d3d2afc6f7e0297e9aa4d403e06c04bc9d013f89cc233a02b9b5d22973032ee0a39ed3f9cdfae3573d3ac87da8c49e30773493e00692aec8ad0dcbd87c121a9c14a8d2792950d1df750e2b867126d67f98da3a616eec81234496ea2f8f367f783d766ccce7e783af9c1ebe7ffd3a3111f1932bab0b177d1649ad4457ccde092afad9cca0711454b5ecef7068ce226c9a520ca41369abc0eb5849eba0f4ac41ed48f6726adb8a1a2f22a458c2589c02f49620e5316efb43fe934ff6cdcbedae0ea0d1ec3b34eb88c9705f24f2d391649b506b74d68d210d61922b2a7fc0a2f219ac10e708913fa8e69ce37df99992052eb61d1ece17f32b74eeb2843ee910b7e6020494c4507e5ff12875494f9aaf4e05bae115000cedd8d2d3f93068afffa2d50810ae455033ca3f138b2bc312c63baeaafb4ca4bccca5e4165c8c83af971db673a41686a1b467fe0c392351ffb0467bd338d0e46a16d461aad178c49cd10cdd0435b4e27945a39e0e8bbd81fe6df23166af606c3fbb35c6c18998a8b815d2ecc7a54b20dd0b82080973609f70a4e8c617c020acd6cf65299be45e4026e2d228a11ff48f03658b641b202b8e52400a514e3e65ee162db4f9fa7a18e35a1f5b129f8706ae4df36696635b3b9c8979dce32be6f7fa5ac65547e82a22ba37c1d0e4fd8e5e637594a476711530805cb815ffd9fc1938bb0debb789be8fe0ae11a7a2c4ddde2905a12207af901243b6b303c9e6d0ca907e0b79e0389843899c66a90276f217097d9f07d2071039e2a3a1ff41b4b4df66fd0a1938ec3845b7dc2f7f50aa8da45c955e716baf9c6f58057aa5c6600122069a59f3311ad24e4d239a4ae5043213728cc51e32a97350100d74709d31eac0a1ecdfa88a6d82b61b1b95e940732add74adfe9649e15cc74230a8087fa11456dc33acfdb13d82d4b6caf63024fc8c7f5c62150eeba1ea69c8a98cefc82f3f7358e7efa3029e25408c336c581e75c97aa5f4c12d3b3062e6db81da74c15839614eb8039204383b46514d2010a02a3c8eba510b5b35864125e80709711e46082190549e70eba5bfa5fe02f19db513e37870a05ef14d7c65e8a32044299a531e81875bec42d75953405a21978ddb6a193e284121f5f2648d0f525c7a8ca3625e7d3c0ba8cacfceb66009c846f7676ca71f056391bb84715015ba100ba235ad10284431a66f9494f841e020e3f7cb9115c5380067e5c163e3a969a891d47bac102ab3c00d490e85f2bac049b5586f092be9966441159a21ebc0f16c79b22bf352c7c26d6adc0e996655f4608f85eaf3c866b760689a5013d280a196d6b023c708a8e76cd6bbe9479be521d2d055ae3515fab406b3f233c8c5d01174c5d50223cbaf5f3e794ae879404b0f3391ee1c249739f88a9125641b566a017219619c425331a23f516a8974728015c8ddc52b7bec501aa33182c07867e925cbbb017fe8fc3f1efe00d8829f50a320c561c5453518302428687bb6a2cf9e05e54e36284ed3ce09d07ee2092f79810e4ea11208e9ddf0832a8f628c3974bf2a80ae1d212e3bece1847fdf008f857a28265649600170e03c143fb17c68428f49e6484d7e219fe2b681b1adf08c474ee995995f395e28c13875fa694a98322b7cab0480b590e50a108a14d8af5a012e3c63e23592d56006fba75f1ed5fc81452bdaf19792976a2f7308f4bc17335d2a4df5fc9664b6606506e103151b99c75dc55b07fb23f79bf13a58eeae143184125e447b8ac6561a0f86732cc0daf703529ca269a5a4bf513e38e4b1715de08bd3237d54fef4411e3a6daeaace7055aab3298ae6a5351d5747865eb22e7cc2b22f6c7ec887d4ead995b35a676f4cbcb0937a19253c99b199d11066d5a29efd8aca7556028c0e6ad407adee589d6bc20161efa03988169799393cd51114d673bf1ab593b92df6f6dc59dc0eed83daca3d06de7051bd43742a16dd52b370a8b195301ceeb564ac511b3fdef93888e178280e5cd940815b8e1a7df52ec1bbe0058909e47122961b463ed6e73ee8359d06c420fe03fcea03ada3c04cc6ea205d939979d9a640e64695cd27ea10a8a60397d7119b6b1b30cc9d91cc9be78475ec1a537dd1d998977c05b65e8538a3b271c309d7bdd4c32229c5262dd700691eb4507ccb6be94b29d33310bc1bc2be2c81924f8e09a2cea055aaf11319ab99e9e4ff9d880bd7e611ecf56a4aec9ab29d69e50c88c576948c4290d52dcc0775e09927ec9fa414ab348542501726c47064c3ca3b34f8f0ef8bb6f6801e6293850b158a8907a2199b3c9fb341773c424cdded32b756aece6661b192dbf665e8597c33ccf8b98da5e731418f1a29e5b20cea388d4e1a61a3b3f66ee64663192d2f192590624307ad8dfd325133861310f6657308b02fde5afadb67bb98e5228a9732889e3c70c700033f725fe765fda503b8c3edb9b810212080bcf76e3a8b7d6fc137b1d9f2edae758eeb5375224fbff93efdf2b908704cfc52573c5494dd818c899ba8de144fabf87b2efe52643035fc9fad9c5743c457efad1cb2caceb029a6e89bf2117b25120203b21def16d510886f46cbbd536f05bef05cb7ef5df83b861cdf37526ec88aa4c06d3dbc36875b39481776dfc1af464ddf355c29e53d1ed76035038aed5f78374be7aa36a931376ec179655244f398c9efb35c4cab56190bcd97c0caf5a3cffd8526921c325da143c83b28e0843b03c81b0add5be411a07e123cf3a303b5cd5b06f745d31873dafc1002c0806f91b210e687a0b58ba13fc98da9a02831edb881af0adddac84eb2739f62c2341cd5376d865ee980a6b8390ad248008215f873635f5aad65ac4909ad482171f9f26c55b82874848f9ac6ac8d1dec49f6c4bae677b0a54f43ba8dbc31a168ad7415df2ac7079be1ddc23139eb852eb203c1fcdc883a8a3c6320ef5dd80d79824fbd0566e0b347122a36daddd05a0440d46cac12d2d6a7a664a40739f0de5df375882ad5b3b2c6d1372eae46209268163402bd77df4725ede28d1bc89d37db4c4485a086e0fa252f93e8f2203150c080ee0ee2c7ceba8a12c15aeebccc33572482d844d2bc6c408975cf3b4ddff7fd07f1a647845cc26d5a2edc465846ccfd665e62a59477f126b3a7d9b9e5d10584f44c4ea6fd0e7150fead5208a90be8554beb8c6c28c14efc08dd87debf043a822055bc962dc9fc753a4553c62f142d811fee9c0c105b43ee60c5ef495c880fb4125f456c896c91633720e18ed7159f0e67c702bbcc8b01984d1a0b7c245953af82093be2a3b48002f8c49b26b37504d9aa780c0cf392d2b1a4ccb7fc96c42cd390d23e29d6187ad03da5874ec70b57191627d07f500788daf429208f4d60c817b8d9a9490b4a93b4ac5f1a90b2dee478d092c9a61226c4fa84bd976ac7455fbb5fbbfdb308475b29494ce7aec458eb22fd84212a7a8ba43580d64278bffd05bc1ce1d0c435710c72c65eff968ba446b8695a7bba16603897b4e5f418f42150e6b2c29a2c15abef3e3cc86b1d5a9df043a97575227bfe67a83348475c11984e09b80f64180241320096cfc6c07749e2b1df8269ef57a2eab36b31d0692b46723ed4a6889198185754332ddb0cef8d23b1612e3b90a62b902b0e9739042fa4205cad924e67dfb3468b9feed7c8ffc14c3e0edc88eaddc353a3f76a33655e04c079b5c2e801c092818aedbe910055b62aa5be14c8d85a07e392a929e767601f87d13fb29dde58bae2acbf862bce29043add479fada22e43c78305065e2cc563c947478225a003f27827a9ffd2decef6e94e719d5a9ce303d4e643203334a50ca8b4868606da2e7a0d7e4a30e5922300eb9e08460b633b36237c817b1419973cb3e6c616d71d0be8b95eccbf7a580a6940fda817268ef9db5da70f739791967ed172f35be4bd1516189f40acdfa22d3b4b203161ef6d8486c5bdda637bab67889e2c24a8f414d21f70df1e3b3218b44c8699943a8754b2dee5c5a2f3d76046b58e70da8bf932810948fb650dd9cdf5d8f9947e0fb2ea2ea8cf6b9b81b1c60199fd0cfc0f3ab73e2dfd9d860b4a8bae844bd629b824fd876b38143a26bcdbd3ba669ebcd515faad665653493da5046568d7744c42316e3328a5230b61f37b002bc1dc335f82806446f4ad2d344e3130cd395024b2b6a3c656fcecd89f03ffb01b3ed5772a6e53c15f7e06c0c37305a43c8e9a4f7043d9c581156c63905ffc283c40ef068289d1271f58d8a10f234680d439e3feba3c62dea692f325a0cba55145931f7b54eae9c1f4dec0f31fbcd611eac0955f3a4ea24125b121165d5d4c07b0c31d3df228967aeab98af2888391a88a6650e655a26086bde9e131842465d842c5a60e18ab727ffb05c77ae66375277e57c6f11e904d95005fa2c65a2339872a3b71c7796f91d6421bc7c6fce3aede79bf992ca6b62f2fe48a28b4251671fa43e4025958d0af46ee16fdbe1124915c20155dade7c5ae4b1a1653427199959827540c8ebf4db7e168ea36bc3549da119233a0985ce1a83b277dc18f00b2858b000d641b1be45e2cdaa6cc117f377a9e34e89bd39b10eead1244e8b7dede0224ffe3fd606b97ccab952c2ec609dd56b5d2c87e0e1f4463598f6792d6180b97d7ddee6f435d9252223d965684028e56c1b59f85c11b2d9fb4ac4aa94cc89a837dd12ac4a8042a52823ed2292941a97e57d94380f3eef3265e9ba4c6daf799d352a57dbfbaf07ace7322880530b0a79a813e456b0ad45500aba79556bec40d686b3317911a6ee1e0a2dbb888dd7fc80ef27e3a8124a9fc8e10052e0f2610c78c6eadccaf0878e9c04a7d1250b2a8d6f188996a8afc367f83b7c85eea79f3d118193c7ca8c5f934f2393293c20478ea0add79bb0c532f058178b75019f430206aad6b7b20484e9c024318770896ef95d3c4065507e9c04df8b705a5626553cf3962d42ddfa79465bc9fece3c9b6d71f7008c05c2057ae8e7c9a3f0fafe98b2f2ac43d91db2c8fbc27d4f3cb96f6c5de5bba7809fb4c743ec1f96dec72f92aa2a848950db3b6dfbfdf0451443caab9f1dc7e06363dfccee3f326e11b9aa6cc6ab54ea9b66d465b281812402cc144403134cf29d633cf9546eb6409a5bacdd265fd0ff5660a2f338f9d5bc744ad6d658c2f299bb3c7c193c328336186c6ae8e288959fea5fba057b734cfb90c7e2706772966f33585fe94939a3152584c4a4b1a42b958919844d16fb426c517aac399b0add412b284b2a593f788bc0d47d91de2b70aa33e1961b1fe072180d4dab7aeac40998b94195054ed4f89f5b8593625ee2013f2929bcc93a40d920bef6deaad20aef3385a8bdcbc7371ef2b18acb15e082543f908277de31267300065a5edca12f2b58b934ef173817b2241a280b49969fb6f2172d617dc0c7fc2778be061963618298cebb2ad88b02328d08fd6f75bb9a1e14b6bd90d69e4ed0384ace3860f39756a279b4b2b1fc4b2377bbfc48f4a4b1e779d6688260ae4fbc9f3eddb63d7866eef8d5a2855f75cd308e1ac27d6cf19cd452dda5b292ffba75796c381a4355bccee86ac98fd430492273e625c4f16933db912ab4bba47dff57091b973d91f4dc343fc8f1b78384680c030716c3fceea5b4a1f90a956d0cefe1835f32ff250e732590d98cf7e74dc61488d613f6569141f0f7dc89d32ccd76d235a04ce3a4dbca7ee02d5b5dcf6d120fc5cd428a278bf7c27479caf94cbb0e32e710fcc3d5959157c9e5e283773dd76aaf0a6d1b612942a6b4aec04187fd6a2bf8352d9e4f56e5c43e9be84e68daf39c2285b451def3405bc5b00f4ce13da6fb67f3ea68f10c4b155c1ce2ee4a88ace075aed8c984ef717a86070a23a7af26eebb8eae8d1a870da02318635ddeda1bd93c58d6e992cd8a197ca2c87e89b32e732271640ffdc1334572b80321d771eb11540cbcec678918b118207836696f4ef7dae0e67f8100eb8bb79716c8a5260b408ce88f950df1aea000b757b1570f3cc3685ac8a03f0055630d47bd3a8e9a27fbe89cd19f4bbed0975691cc5b2c523c016bb88e2cc72481b9ad07d005da50a5cc17a3933423fed48c8643d8de74f8120f611efd3c9d22f9c865a7cb692ed2943f29fbee544c9907f5e662de3dd08aa707ca3475cbedd7c3adffad25749ec1aa6e6e1ab27eea1a07b31f5e2d823015fa5c4d4bfc9b3ce60faa70a15e17208aefa6fb800dc6c79f058cc31772f4513893e82b126f4ce129f911ae19e121fdc48991f12fa483e70173879ae448baa02e73367b9c1eafe4cc38cb212c13fe91f756f56438aff2a5d3813214af9c5a5acd6c6f51d12453b26cd6c2ff01167464f6d6c5de9e125020f87a768f7d81b6850e2201ff1f78d891e3fb45c39c77fc7315454cd06861016b8d1405b612e393b7f587182a15a221ae9c17e2fbde401a3fd15c26469e5ef211615c677cf6c7a05fb6601af8fc68a47dd1dfe608a3bd00caa4997997d83fdf1a502e32ced71ffdd6681dd4b15c645aa4dc833cd0f79770aa4ac144bfa23661c5054dbdbb6ecf2157679da0c047e8560c9e96012e55550616a240619f97fd0a86aaf8d11c7de03ca3e5d2efbfd9144e2cdea1cabe4112f3ab5bafc0031693a571f3468aacc0cecbc5b926f3a77d3cd0941118f50f167e7d8f60cdd3e4c482274419691c8c1b1987081dfe02422fa4452a075c1bab7b2dc80141c5d1800b1dd4a9b5afb2dd7318d37ca162a25fa72c4f3d61d2efbeb431072c61b5efa7cb6850dd2e538dbc80a3c9e30ee7535b45dcd786a199f1d587c59f99e7dffcf21d2f4758f90a8167fecab2b59216cf2dcea6ce082d0f245ae26dc76f730b76e6c6bb6de38c35ce683efacb13dd55e2a3ac1fa84abb993dd8f3e86cea7fc559c1c282c968b5c7ff883f5816412ef36e331d03a9b1de23093025f8db81f922843300c717e7e6aabeb5ce30545dba59709b2c7d96662eaf359f3ba13f96381e72bc144fae38c06fbbb0154f36e1d804639f7dfd8ff0cff61f4f60b694a56b29cc907e88d8c00ae6ffca7b08f4916ebf008aa7e2fb5e8ac8aa6e0b79d477a851cd394cb6f549aac2c7b39a0b5b6be46fca819b9bebbada382ccd2b33488a9d6ff23c497b70538b49b02c396cfa44e17afc6487f1efee02057b1f6ad10631e89884e93763f3c84001891e522f8c7d9b0dc2c2bcf7dd6bc3fd05e2d8eed1c2ada6c5796da48a38def25ec96a49da50b903cbd1ece43f76b65bd70d42caf4d2a6e8273507fbee33ebcadb874d1cd64eaf9eb7e605c4d5b91eb6bdd7eea956be143f304248dfad597c12c259159a4b0cfd6863a206eda5c107dfad8608fc45cf3f61606e5182ae753822942eb9a98330dbfd8a0f9ba7f9db3d8c9c229e4add97cfc257128181732dffa5c6c78db6fffad2d058f6209a96b8bd69e61dfcfc7f53118375eeb9f9bd206a071c8d44290c19a0759a4d10b6e08a5bf597398c34b4e5cd6e73e730d44de8003a47940d8d05eab85db69948f14a889f1f49e35d51b319991961e328af1e18e942c0ac3f900bd736a94aea17504a85d2d03afde1ede3908994644e1277cce83ccdc595c1a1e2808c7f0b01009ea1b0244711a7c72a07bddf4f1c3dc22a28e879099a749dceab7acd1db6b676185c2059e0dc08704378874849336cc4c971a180001fcd1578367e932b102693c2bffece4df0be8e5832efcda832c4d9a33b31718a5ff5213300e27dae1095b2044208d8ce859d196102841e433a536354d1ae0310d42eda58e9142df1eee411ce5366f6f8a2c8e06265af8df8065e012e05b4e5ac649eeaa246ec579c6bb36c24d600d3182238b47632ba49e264e43948a849aecbdd6d499cd856201a9cbb264136ab03f9ac892851d583cda6e3194c8eba893e77f8c5459731ccb9f76342bf376a9bc590129688de157b2454d498ba62822128388dba7af0d2d2760e6bef1c07aacab830d7ea594c135e56ab939091c6b2220840f02b0f110eeb11e84a8c181f63d3b95258ca064e2b2d72b15eb5b09cb0cc1d003d25832c3c42fe4ab8584ce24848d0155aa16538dbb2cd294d5e426c0625655497b06a7d9abd106c6a1cf01f87d20be67045af148f9243b7916c11325325512970e7631a01577b4fa1ea4f81fcaf54b10cd20c24154cbfdc59d808defa5de967a3eb8860735b273268be8a234085803db2c00a99d0a5c8eee25e61e384a1eee286365bc610ef460e84371984c5f3f38442c567c208668c1191b51e0b153a5e023a92a2b5b2965114c9b369904284be66bebc0d48ec3fecf3cb63e3b59b7d8787bd34bd432cb17c5240c1af29f9893b29aa6eabc52e1751ed041776916ed5d005d0d769b2dafb2125f3dacb9e59e25e5acc5272b436a46bc482a40c81f80344029b746e591e2549723716e96f87ee7a6ac9958884e0aa0abd8b9a40a53484b2518ae6066728c16d87356d6b3f83d5127a688be801169dbea85ab9f86f612c0e8fbc744390357ebd4d25c28f9d65b3090b8b1d125b4a5f325ea1c03f1aaeb2ec1e0ca36e8b76f1980d1eb10857fd557b6ad7417bc7e7584624682ea1b82b5175387b6fe333ea81fa0dd58f573f6a352b66e537d8972317fc971b658e609b195094c6bfb0d75b45a9b59ac89f122a7c8722f8a14882159e2d2dd6239f01aebdfb1a8ca3c90258aecdc40705bb88614622fdc15cc918798a1beb3e250234cc93c9b5d6696698df9475bab19e8cde596023927f748084dc8bd6e99760f1ba21e330484a9d0ca7e16f01600a956bfa14e2031695552335c940bdc0f082b18c1361904e554fc02b7db7099258b3d9068517c11b7a35bf054d450a38207e415556e73b1de589aecafd6a42f21588cf0fb7a1d869442a85e6d03a3baa9acb8dadb14006dc014f6dc1cf56a6839abcea3d8009566b27ab5fc3c80fcf42e51adb08c5fbea870b5b29c3e57af07cce911ae1c90ef6580e8df94ce0ae83fff5a6a981966da3cac3f973ac6e3a54348b2abd4d1c80e07125a9b22eb29e1e1e092477afbe780d9bc4aad8fe2d68dd853df15242af9b4a76f471b7d6da7a3a4a28dcd1de08909954eecc772772093522e31a6ca745fa398320c3489e6209ce53186cfe5fe4d67faf7f9d2be7fa333b910fb3ad15663537cca20051698bcb32654a9370a2c766b4e91ea7446d18234905c085ef7008e678ec9d68a62f2897a558b31eef28a3f39a2a4273d6b9209f6f37099389e585e6232b9fc463e02c2344afb243d79b1896bb6894314904fa9e69b7002b365f67545f629fe0e680382ce6ac795a4919ccb00d7c2cddb9aa47d431eeec84bb3cb5fb44def90a7dcef58a973d415b28ac1f8d968afee68d9d296f97710a772abb201e11a4d3da8b15dd5961e3d28d17cedcae0739e841eb90547d1e642d3cc0b9f61221ba114d9aaab60ff237e8e74843836e5614870a57ed96300a67eaee6717fce92468fb15a4b74337a68173211041ddb80d8a3186d92ef9dcbc6411110ff396454a7efa8c70fe20c1302c4a1fd29a66d02240fc6aaec7baaa3ad86ef4998079924aea7747aca4ea6098001cc8469c494f4ed2762e11b5a1d86610591bef158b29188ca4ba9ed8d81cd03a42faa40644464cd2ca7e8d2cda6cee7c33f24e10df9819deadcca14d4826946cf0efca58d6ebab789afe512a03e23a49769c6595878dc482ec5c7c5370be00bdeb695fd51a598982d8b1bb7000c26155b51df0ab80cc77de3660be454117b2a52ef80b9318065e8655aab140a3ad65f4fb034620a9f6ae7c3e4e66bdd9017c788328ca37d4fddbfea2c2f8a6ec45feb2bd0a6e7d59d284576359f4d3047d32eb082b575d9f007318d8dc7bc46a33d91376a912e0523966a2a7d6d715138438c635a8e0e46287fb78cbe154ffcc9d22f45d9215ca2046414b98b9c3023e837c87f08f8a64802a11b9edad24e571c27b73e1cac9cb727bba68e44461263549a3a820deb64e12fd35f5f0c48a7fce69b9cb32742cdb7167efb4d5752aad10442c444532c7cc894526450e114b751ad4fd684e7a9993680c2c0e794e33ffb656c7432b9796428248ab6c637338e1e4d4e5a02c9daa7f303670775496ea0c07192a25f9b484b9e60a369d91931ad91466fc0c9fe974c2562b4b34d0f8e466d276f768fd44752074c4646337582f34bea81138b30e3d302e583c9b1c08b40108ba4e4902800e53287d1112b1d20b6bde3f138cf6102cc6b5b653666f31918a0f6072d3c2b60dc3ee06a70a44f9d8b283ccb96fe03bde6578f9674b1bc9c37222a8d3c4a3575a6c395a9a40262f0ca26742a436d2f72c02f534065f965f5e61d12cf651772bb861e7f89e995661c5f17db517534daf4cafa81421fa0f1277521299216a3283c89d4137f6351ad9f96fdbda547146b39615adc945045dc24d7abdc44fd8f2f4343006cb4708334eb51254702780198814ed0889226556607893922733767c7b763b1a43c9a52f2478d04facfb02569f115ee2ae4b978316c1272d9fa461fd72958d233168600f73621a5ce8b11ccd6977112845d95631b995030cc0421c790d4e8b546095cd55b3567bef81b870dddcb90d87d4a9e3696c878689db63635b18d74068a7452a5db462c8c721151f272c1c90ff1a4c3f04b73b9421815c74375d2c0baca51785bfa2b1fec977944f1dbfc1d67d734206ac42b02f16697a05a9c070ae441a10d354a110906933107a33179970a2f81633be1f4224e7f8c030ce4903f39d9cb6992e7577832f9f853e94cc8cc9216aeff130dfdb36b462e6e08608cdfc83b2e5d0fc072410709dba730d51bf5f51ebdb6436e1ed59c15c1cb75924056ff34ee2cc8a5fa7c6143b17aa4381bd09bb831c3c91f9465b48383f2fadce7c0838dd1b9c1d99772e6cf2ff4906e33fd12167033ca4b4b6c1a437cba71f663892d4888a6d252450ea3447b41dc10270a02896f2d94eea68a2620b4665f7211c131aaec49a777cb7ee5d7cc102433077c97cc2c633de847d171fa5d51cbc967b8065e14cdd242869490ce708d3c287a35f5199acd4b8a8ec91745c3dd178a0a7f2ec333fa42d1b9fbe60a5efbe0a2f01a1b29b3a7322ca30f149debcf1505db978a7e6028c356af51a6f60e8009ade4ab5565f6d9ab7c52bb84a23e5dcb9becac8090e0436ffecc4a916654e9d64682bb694c4ebd181535fdac96b271c7846ef6465f32f82cdf8d821d5def2fb5a7cf5ef83c1a940f4df0c0c65ac49b90191f39de13bd8e3e406fa337749437756b5e62fa98674ac9198effd54153fa6bafb42550fef605ce3040b78ad18beba4eb9399687f0af5755ce81dfb355fd05b67e06a5f880cfb88900297c367a93b524d8d8b0ee8c7988e0084d35722c242c7042fd5886a1d81b3519bb49b1d5d0a0b73f5001e7d5a912b1b922ccc63dad8987019f68ab58fc90db48abfa62644fc84025e8ac966509a84271af2bb4161d6dda6f9039c041bbe902ee437ce54d4036094fa946418a3414bcca4e8bb7a3a47ea785e3d96f361c2147529a7bbe0c3a7719706b3f4f0519e959aa4113028fd2f686bbf07e2f442db85b0001072973d6191da197010476edd973aaf4ed5c042b5f790090787a2aae7a5745c424190404dd59abb717ab77a96e4b711c194f12aab6f068c05a2c0c4c6ea759a4147f743bd18316af7224970efb540521568d1f392ba8934ff1f15343017a64ced05d847e0228bb3f38f74b237c6043bccbf6bdfaf6b94aee9ff220bad548e22ad1944bf3673c0770da2c76be9d1b17d8501617fce2e17944da10fcbb9a1310af3f6d944477c0313ba19b66c1f8a0c9ecb44d0bdbaeb9d04532f1c1daba7f1dbbd9d61bacce7dc269f106276472d8f2099f552eea40406ae30107c892a16f09272618ad7373ff414bf8134e05e6f2753ef94a3a66af3ae6aec7296d0a2455fcad6aadde2885ed05bf16ea317c006d27f9130b140d3cd3840e220d83cb18100e13f4865966b2d1fc176b3dff8ea1ae21d19dbd257425c8e236e7c0b1c153d2b5e6b2d3e92915ecfe619dcf873c840a4db7f10e9df1b263a96f1f4957c991c8be9ac4363fba6c039bf97f9913f6c15f3597ec6166ea97d3fd0c38df737df2d91035e8b6e76e66f31bb2c9099d484ffb4c0f3450b6fb1e319b19b15ee6e64a64cec724358e00665c2c465eecdc17aa733ef0b3a2c48f001ce9f22c63cea55a48030c968a86ddad167674215f4c8bc43ef083781dcaa1a2d6e807ac9702b76a04cf4d715215f4c45df906f17717334726f8f3f6cec3843ba15eb08be75d2d2e29298292caf236640be8e6fd01f49fed2058648d2cacc6c083d2992e796a5497d5d1334a1cd9c19d285826cda734091708b7aabc43e14126d697047534a43a3bfef9ed1b0b8a698fddcb9f5fb3ece292cb4fe675ea8bf64b8cc40e5ef732133bb1e2cbc4181ccadf5f3816b8830de276d90fd532ed671f9d6e739a1b4c57d15cd82ed0d9b3aef7738ecfd468ce85e3787da7e926d1fca5bdf2e0dc58bf7d7f2c23330c476c131e5587f8cd4b0d28fba4c7452714bbddd0a04c21925fa3e94c6951d732d86dd7697453c0705af50f21a95481f9bc9af99e74d135d1b243bd8b4c18960076d69af3e582faee091d755e50e9f111f44cf95d7cfc5748f8606ee03154f065f0b7e67c7ea7cf918a08b6479732ad5ea72de2e982f3ed073f46078deae8ef31b5655a70171dd55f600d277b2e1e474eaea34245b82414d050088abc09c6913a7edffd797ed8a2946d82788088175e9fb1b4ce3807fd7916ce06552895b156c8d9df64475a524681f073a1c735a38ba111639af99d325435202ad8d16464233a224a81de51db791f9d64b3e42fc69d37cb0f5efcd2b46813da628d5d16c26eed4441132b71392d43163bda691ba821552b885d5a86ac847557949ea5fd3281e513ad4c9950d36f96737fa35c98d110db6866cf85272f7e1ada75b2bf97bcc4c7c4d4b5f432fb217e469a3f0eb03f47fa58e6295de2bce73635cb11adc4ef3be9b40e081c87d40f6d996c99adabfe58bc00b76adc15c35e96a571f0fde85103b1b58504fcfa5a0804ca5e371d43e83c5628929513b2f58f9ab0a131fc79395414857bd59027b94274a865a094e4ca32852fecbfdfaccb1eeac976e850383d023d879c88b398ee18e2697b20f9d503a3263028fbea6fef18e0f32d0d511435411abeae94a506c7ad2da7a3f13c3fba024de214ffa738592a4abb82ccc0dc36806a91fb9ebae89097f5771c643dad4ffea5c744e43b4e2735eb79ef96d965f5e1a1d8b6b8bcc79178e078ebe424c8e61017cc9f0f480791ecdd50fc29e7ed694123ebdae0bc92c62c704d30187b7547af6c6779b147bf3d756b75cbce4a67bfb233037df5d123078df8365cd88f560d415eab632de844b65b85c507bfc263268b645f8c4ef5205cdbe822ffca535d06b6bf896dfa40caa59e06b7e291528b7165ff94d6b4059abf18d1f950834752d3e8fffcd2a83d6bfbe4e373fff24de2d99eac8905a2ebfca0250ea8ff8df3f6818a794bb32eb51312896bf31f6cb027c2dd0635d5e014f09893eedfe0a7cca48f468b757c0534ca24fbba7029fb2123d5bc0ca08b070828ddcab78853b264afca52f04d58c3250dcac9b93a00afadf98c02175419c08366a53100fc7f05635f9a1f57acf1a861f52946ac853ffbecf6772b18319c1af8d16dc631e7d1470fd973ec9ba9f563137d5f8013d83f976a64193feaeb778fd563072f300e8fe6ac3af46c9bbd083fe34ac88a900fed93327b10e082e4b8db5dcd1803e0452f9555762de5402e85e3ed38736301858600f44e23aedb2834c6007b23d8fd62e94e73fd989280b40d3430867e8ed25e011e2d66aef8c29e6651b0a0e2871c0f50f1867f139e325d7f8ab2677dad43ddd1bb65821499e01627c28d53505a4ea5515f9a611d1490d6933f2001982c417bb58b5ada4e2d916aa16f8830d95b858528f39c12d76883fc9298da61a388c01580e6785886affd20b0d7fa88fe5c3531039e7deecac6db090c208b63ca9e3adb937aa45b2118446956ae141a8d1e28465411670e1fa7d442250862992d1d244c8358491c6f80516f7f8c61417e4e971f50b069c975c3c31556724899befa3c0ddecf9ceee3970683a5b2288d7b8cb113d2c7853153fc631ab0216ac14900778398dd45549461909924cb3c60eee9f151c360cfb0edf14f0ad3781c4f651aa173273dd869bfd4f41cfe4da02b66e2c25707a353a23901f5989822c01cd5de92bdb44698a84ce5203f295abbf032f1171d894188eb94e145a41176edf7aa66a331ea8f62983af71214fbc2286186fccdd1b270a8a2155601c11e31e45a9c6bbf52fde3a8c00d7d6aac367ab30268bdeb8b23608c5fc50646d5c4c21727b595638c2229fb07ba834a6c6ed79d1c8c3d618b34c252bb5dbc06346c44a7c1c72895aeb726bd024aa1a4de405ae5855c9310ab18bd2aa696f6180fd07da5bba0dc1286480917cf6dfb42da5868873a536c785d37800677dc9136bab022b295dfe485b5e226842273194ed3bebd5e5c78279aa761eeb0f38b55378cfe75b0f57fdb49deb7e23554be17a64fba87970c13b2610c72d65e14ae9d95ec23b99c57be100cec4c3b8a2286096c6ecbfa5f5fcc66fc4acadaebd6c9335428e0f957c8cec508a97f84241b700e0b8384fae995de0664045dfb4f47bb3f5e15b55d6d395982ebeb6ecd4f9ba5f79f74ee983f625756c0a0275f0ad897bb2130b0e2e82c2cf5e4f9c500ca0153049dc7700235684e6c66ff7f9951ae30cda9547f3a20685c4f9f9819cf6c974390a29bca20dc2d1aa8ab8f7271af57ba63e120bc5e4371de520d534dcbdcd9e3c4572fbc8a493586607b430b5816e52de480cd687eb6c231e7324fec496b17c8e6ba4d105279ef70ae3234fe095c4ccd1b3866b11e9ea895621fec67a2c932cb3598e6b1d1d3c5aa34d5e9d8488c84649e6ec1bd8f3f8562bb442958c87ea24005c70e9b5fc4bb2ef2a2bbc154147678aabd14406c8ad8074fc098c93a16f2df3340142de2c3aa973b1a3c062523eca51e95195e3c00ff7c354678929594869e17b8fb3fdd7553d02a1f25b832fff7a915c5aa121289205b1b2b15c422edeb24b2826f65df4d81a3232bf96c5c197299c6fc305dd9a4c05be4fd53ace9774439e0602830364c56489e2b4a5f8273157759969cd938d0ee0c55c4a8892eee2c623fd0e6b37969882e704d402950a1e3b0ac6795454caf80fb74aacdb017082e9b4d05052a44c5e971eabaa39cfc0d074deb662e5066bd70b8cf27332d2998c46f95b105cf26cc1396bf59b78a3701f122d33a30a40e62af7bc51437ff00d75e0481b01b85528bdf49210522f3b01a24a47535e0786f686fe0cef0a0c10dba804d897f4939f4a1c2888cfd5f99338ab1f0926d2bc474aebc12da6f84c49edef6138d713002f6161657de04429fc8e921292f65f1d5f3400173d73d611e9c1ac6fcfd270b7627f90000f9fb61596c985e64aa6a0183eeb7c0f7344a04c001110b22e022fabc5aba3edfcaae7aef2119d2f0131b0fd70807de8711989481ab482c16e289cd4c2354777000f808da821061021506b4e92b1515af0154af698b106b7f7d02c5adc2ec557791e8988c15fc4be0a51ad9de5b2c38730c09a2dcac3862c446bea45b503210a54d4e9817a2604faafe92cff0a33114020e7bcc8c6aa748c7b8e9950f21cd9859461a7581cf07078833d9e18ada3661df4654aa9eefd59ef99cd8564136f6f1ff48419a4c2b9ad313104653b3d4832dd0f94fe1211314865ba9694e12c0cc94b1d2205351c3e79da61f15b498e44c2041c470fc55d35f66006eeb923fb90a7b8ad0f1321563f37a1d2a3696d53a402671ca50dc4914d0cc6a32715f5a8b63f68db51c114b56d543c0f5458b311edc0fdf9e0d7d71b4d522d0e661e3885196aae02b180c64e63331b35158e5e360936a175012742ad2b88201dab4b4f645e4504c46956d32336af9b3a9e11f3ca15d070ffde2d55f087087337e8bb42c6fc597196be1a9fd21d6615287010b1a211bfd7d2afdc0af2ea0b6942cff2132095d6e9db15e11aee91f05ccd7511ecde462b34d3a26d67f6dcf9a2f1a99cde008d13d632bdbd0d6af1e175438082075e1f21217dbde1191349ef651655f4a2c6375b7e249fbe032b7081cffb2acc7f6c5aec3d8b00b97682b8c20c547c0dbc0464f3cd437c960460d6fe2d03e689fc3363b97b600ce185a277953eb7c809d35015ce854779e85614a6ba15238181c0e970294ae700bbc8e2385cfee8aa7bcbf1346eb3facfd0fdf73ebbad77f8c565e8afcbed5a33468993fdd1a1f4985747a51141028598459d87ec4b953b1e166ada21515cb3e07e1e62dbf4e2df06cb29b61f03104af823f00cb01ea2de7033c4c11f2a98001ffc22d986b0940814d55d93404c945bd116337270396f3723c767ce33813d6fdd793534528ccb4ae104e014db8cd7ad0edd8dbe1c20387772973b6c2ac654a4f4cfa1b1412d0b278a55751a7fb9d628b357d5524a957454f9d58706a1c6aa33fb970174456013a143ba3dabd72c9ae13fd64afa6dfac7eb862eb66e4bea6f31109463351107d7120eef99fb054cb0506afb4c2e28f366688a0e8b82ad329583622531b2862e10f3891dedfb50608036076af302288ba034789a0992b0aeff4af4515f52390d33af369ad0d91fc6110b825b3c53839213ac454d97aa4261e7c5b3454edebca5bc082dc111c0888ea816fb9585c79ba2d675a6ab4246064507b9feadb52a43e3e41381a0114746572898a58f7cef60f3e10e971ae7f9db798502340d6e4e0f62cc5bace96a3a0000e9a35405c8d128e4c132c8e5e7df082fa1148ae267608c2b87f4e4e46ffe5567dbd0d05014c638031e79c35c666a82d645d913cb866eeeb82c0d7522d4b44cbc8926793fe30dd2c751bbb3fc1a6a05726102b07b6002680a3f626b1fd2e16d8282b5f6dee81c7e0412f98cd856df53facc49ec286707ad8f4a83b288df6e313da163a0b81b31cc52fd896ed221e95e84b2ed5e936500543d10ac823161f5ea0d3e1e565de9e1694f6f3a11ff94f552d547eb67a1aa98256fc6fd4adb2872f0317480f50d7501fed312a3c26cb0387b0a47881d643718b80e482f1b5a946c13a5cab8e5f01add6584d02b59c61fe9fc41ec06da668c309d210d5e4d65d21960b78fe304e3c3efe14cca44077541916a8ba1e9d577a3347aa645d56252359ad52dbe233b52e37cc485be9f478900671599ec28b607a14a14d82d0b1cebd8b7c329fc30a056356ed90cbadb7ebf19f9ef831680fea1b41e14d15d6de416b9c7fa85b30bf1217cc13a8cb4d7736c3c1132e7712429a7060ca934890d151732f2e5c2de6b7a78cd4bd33bfee91d05772be0e29fbfde0843cabb5156fc21f51180eb3c482a9ec42e85b6d3b5a2bb4abfb63dc0fcaf902ab13bcf9d7c0fb9a0f2e7e61561a62c59e56e0e28f309875857f418dad4fc67a9c6bc7da3b894ceec402ac69f15c52657448c314cb2d3aa48677a744c037872a74deaed3244d72617a9c05368e8a13d1a78a9c427577bb8563166761a88d6af90f2a41cc6c4c40ff218e5489b4c678262c3df8e214fe9a89d03e7f40cabfbfcf116ff235e2a3c3d3795a197f52f8537d12491e0c420345d25e04add32ed440d3426264395782179a929a482f50d15177c9719e3f86b9212787076cbb240e4a04506693c73aa088d5ae0e7a4d8124c0e8c466001f61dde738b80a28f462340d0607eba5c46e6b20c2b8278708e99706bf60f5d323b0e2618cbf163483ea983aaba076071e0ebebd37e16173b16138d21e90eba08a43ccdc6a689685fef627f1ff2f152bbca78a211f802c2ec46b24cbc8ffee871a848226f8d8587c9b5af44aca7f6ce99cec8d96bce6c6e2f225b6d61fa1b031fbaf1c4677f9f514dc9cfaf234bd90e20fb1285895b1d4df267542de5044ce4287c2af8dd9c3d1f188127e292c757a578b19b20281f50f4f87e5453b3d0d7476a180c24f7b0d9d986a7ec1537c4db806de5f25b628e829222ea52e7763b0fe12a9cafa03c821a178a9e0a2e5c0d73a8ef5a87ee9f04eabbf64a8a86ffe9a5865e53d37f37cfcacaafe2c2cd461c0e983e8166b26cc8cc36a6304fb4e8f9685bd75b433de5ce23ce2f6a4ec20722be2a0e1d12ba801b957b5f67debb6d7768a2acca295b80c0dfb38612271c658b4fbedb58a1c6a50c2b9862533a0363ee94f77179f1cee6657704a4691bdd2259ca2dfc4688f2a1402eb807cbb8b12075f3077eb54b5a810378f8e1e1478d0157f4cefcb8c0fad8857355029e2ec62a210d7015b25a1acf8fb84f288361ecb1484254f9036bc0b3102c5465d45a16ca1bea6c6d9977969a047d926ca903b4b6c4fd8f8f52809ca3a3f82beaaadd02699ed2106d48c945f10bb4614a3c8a4bda4c38331d1e47c418f74c133959cc18c6318c360bc5f60f23ac280d21de2e58a7e7b3f42ccc72e01f786fed75f887b161525202ebb5481291a45bd74592cc99a0d1fd33300b9c699204ee2521aacc7f8877e8f42311a8d103ca47262573803eedca89c392351f24c4bab1fb2dce0defa4193e6c992648ac1e728d085d6e66f19b7b71f9f5fa40a503a9ad0dec29035a99868457205e515107a9e5caa1a5bc7a8f2e5e8a57614fc56e1f6a840dc0e699b4de01a32fb0f88b3b3d57001cc422f57806c0e245b49e297e031a3d0c260397b66deff559c28798dcacc050f66361addc7bef265bca2da54c52063408cd070f08a29fd6ce288d8c8c8ce8fb7382d5da0b661f5f94b21a50bb5c9d2be5f2648abae64b769e3767360541ddf33d107e7efafdd420f880b80f525103db67c057be0d14a78ea7e6cb55dbff6973519719f2f7e3944d2d7f06d11c88afccd9fd74959d6eb2d78c35b896ffd89b4cd1973e415a94fe8e8d27ef9db299af8cd3e5b2d9db4f9d29f3d98236bddb4f202a5e7d84a7b639f293862f652865f8539cfa886f1b4d976cc43f52688f12b6b3fd364e9abdfda6c446229544274c53d663ee4f67c053dbcf1925e80a4b15fded25d577fb23601e2474184c4871c59d734e8fa635065c6d271292a529459f9117cf39e794e27d29d1206b122719cc797f5e2c25e6380ee73a591b11ba4c577a54628b5e8adc6ff87aadb5d65a2ba7b156e27aadb5d65a2ba594524a691d43809b1065baaa4f29a5dbb63d0a6ce4d8be0912d83f51d56cc2478e4d37b13dadef5a529500043092404a1f640fa24d4bd983fc59459963db61d259710919c37943133e405c8003117290807e757777777777afb53e0a6ce4a8ba09aa8920d2269d761fbe04766f0add694bacb2a5775e279dd20e749df431e74a0f0fce4d0c4bf6a3e36ad1d084603d4070646280642f9d9a568de60353910feb018203247be9d09a1acd07160488cbcc80765e43b11a5a45d2904ee98bea5e51c31b7452569b5a33548bf9e404a133f447d08c678735f41373e54c9a2d678492e80902a5cbb13c6d133ae99d74772983ae9b2e250d393d9c3ef78a2cbfa33265e7bd225b946d3a383f20e89e2faba441c110456dd3ffb9670dab7407c2dd81d09656d801cd1e5f07339f93524a3becf8d613aaf4f6df291974a47006182e797225a79455573a67139d44b942a7a315e6c14a30140c6506a6e53a6463defed832345b7e0d72185d7135ae0683c160dc908846f4c3471413e386442cd429e67392d527f238612bae4655fe52c4e2388eabd15595fdffe9b4b99a57ae5681ea50a5056da74053cc58e4aa5f7bfc4cabcc53ce93c7cad5b6bfad19ab6cfb9c3bbb25fb1f248b6f972cee2359bce6e5403240609426595cb2b864713bf3a2dad6e5299fa9964ab6717aa1780a65bc292b6546431eb34fcfbf75cd7894939d29d91957398adfa837345b5ae126668f5ccd0a3e88edefe63637faf30b42eeac3a43aed4e5b4a0148d3831676de0d9520a5b0a6943cceec236e419e4a9191479c6f658f7e88d796a6fbc312b2ca32aaff6c6ccd98da154b6fdad9e42596428f20ff795fbcadd9f79fe9ea9a9f29a41aef2c73608f7e9b07bced690e7d71fe9ce6c656de88ee7c206b136945a1c0f73f3ce3ae9d76d6d90205fd99cd59ce9b09bd33688903c35edf9d106a13c9ef21984d2207467b4fd48775c95091b86fb7a77705f3971949ef23c7535be3367e8f215327702560c13ddd3ee69f7f47e77823c9f5eafe3b69f3f62e5f1f735f99efe8ed4bb776f77efa7aae3bff7512e7d913651edfb764e51fd5ee877a207c1dc223d8308aa25555991b71be05c29a5acb7459b31d259f5471644298fd4a1f267eb66d3b79152fb0f4b9dc667ea4b1a69fd478da6b4297518783fbd9fdecfdfbe8bc9d253f31b491515d9be64228fbf3713d004d43b527b76ffdb6f5bd6534c558947284f715fb747494996ee3739dfb5896a6f6fbf21e67b262ff33d6dd2e91da9dd7da7532098bb138944a8acc8f2e584f91c90cdfdef2ec463c91e477bca9506a80a5feeef9dd3bfa9018a3137c5f9b95c6e6eaf81f9c3575a1b902b1b54851f633c4a366cccc4e6221262063c858ff0147e49475e69e3b7f86d60b7a1e337f7dcfd51ce342059e8c6d509ef4b8e1f7f4a45a36c3c63e369f3864ea28c13b482fd6dfb5cecd78a31c694e2fa9bdc94528d040d42c5460d241c63ac9f0e81b5132620427dd1dbd862904795ed239652b5abd440dd15967f859f3883cb0c71a8220a1be4a00184274c50835a0dda40cbe1af624345d2f094a0e25c0ca6538fcefb22ceabf23d5145e27091a86235112917648c9f7e4e9cb04c595bdd5224580164fb4fff4da7cb3ceccf5977f80e11ecf91ba554a678ccf7ff5ea69dbe0293176b7596eff9d856a324be184f3cefbdf75a91bd17dfe771f1b5fb3ee57175a52fe98bc98bc98eca62edba6565b0389750965e4a29a594524a29a5d7524aadb5b64afadb532cdf6eff71049bee7b37d156ff4a675d77f2159317ff8dbe2df95f2b6af6fdfa76671edbbbce263b7ccbafdbde9fdf0e11ecfa57ee39c12a695899b27aac92477daa4fced375965a5b7760f98b8a3158a12a74602b9da215ac49e956b7cd0e0036ab800511f286a01921704881102b64489badb358e90d405e2b152b8bb5e7f752594c6cfae308767daa79506df2529ffe10284fcda76ae8c2f7f2e9515223114bd5c4d97f7e0d01de5d084e99f2de8e6077563aa520a871b88fdd7df79d48f4fd89a04cd5277560bf6c9be20fecf4f8814d52073d49a3d2fd612c53d5ebb1bdca1a82aa25e87509b552d4a954e9a44e88821382ac200a55c8e7c3662bada162e38657f2445b0aa982662bc20a9c10e48bc38b064d610b59a8e2892a0cdde08a23a220d403254431030627d49057c084d88615f4c07230e0a15c1364ac1456e004e7c31652450afc25078727a278c2cc0d40f8c20c0f0fdc1094022ccc64c1832cb0b0a9373ab08196a1d6c052d1d5f001ab0aaec0d5544cc51aaa102ab2286d29848a315001863d9e609d140010430e5a7110c2908fcc909f2139d8810f0e6ee0832b365ab3b193e52ea55c354bb2868d1b282216fc10010b6c80830d4f8e31d32253b47c8022085ee0420f1b803003191bdc5208d0149e0ddea33fbd128fe9a1bc92974f25af943de5d5cb35973caa7d6ca76fc0abed2fa61b2c4f8fd2fa3665ad88b26247931ddb6e931d73d77fb1f5ae543cd9f50933c784207ab9b2861b6cb206a5a7df95440f84a41f8923e2dcdf07c33de61e8b1b634ee4c13d7e9238729fbffb76848f5ff4bd808fb3806d89862d851001da9dca93be62e2491af3e5962f74e32454e89ebb7ee77522cef32ce8987370a4c7126c4e634f811ddd6cc59e02bf7e060235fec47a63d3d9ae1c1ebd1ad949539cb9e2c746d952481a66b12db7dc767274c23ac93d91473c775c355f723c9987ffa8c890c72a622177d6c39eaf3aff2a8e49a4b6a5d87f1379d4efe12ba308e52b3c44bfbd64c14f5f46c7d9bf5664edb593f3cd4e69c198ca1ada9e56cf5e79ebdcf55a5badadaa19962cf7fdad64c1eff5b71fbd4ab7a7db53913e16e5a69b288260ee1933e84bb9e33715913ca568427f4c6d4b75aac70c4963ced99dd2fd5a708a2b6ab6dcd5576aadb57ec557a4efa27e2e96ce19ddf5b353d1d796099b29fef5a59e3f9eca42421336ad9eb02c9684f26ffad933cd9bf5acc64e1f5379652754836af55adfd24929a54f7350f9d12b80ecedab8e2cce28cb483715a2aaba69d04cf1076184447acc41b365ce668a3fa783b6efee13af4c89de133b99128552259a2d73e66d9f4bf809e163e3af769fc5b93f710a899306bb53e877f75368e6e0b4c8abddd70f88df9d16c93b1d1c7d6ceb49fc24e1ed0877d5af4da938d67189ede39b1f17b830fc54a4949be7e7b6fccfc5f49fee48e2c84f99111c4e12e72ed5adfbbecfc544ee292359e6b6530572857b92c4f8ad8cae50f48451957daa6a4155a797bef2917c37855eccf93d8b72bbbf8ba207268dc445b9391808e3fcc67c94f10c9ab449bb3ffafa70afeb33e2deddb5733e6973e79f41599cb4fce30cda5668d23a92163fe2d3891b11e9abe49c47c4dcc247fcc568846b713a9dfee4dc8f90bc2df192934739f722d1f6e5bd38cd83f4fe237a243df71de805b9ccf9cd6f8a3368d2388ee3384e85e4df7123239fc5d1db233f47de4518cf891de967d0a49160e8a01f8496f488abdef478a22e9e937bd4e2678f5636379ed63b41959c449338f6b09bf42ebea32e9e7e2e262f3f92894884101f02c29138fa1171f4b147de3f9791cf39bf0eb9f3fbd82271fc50fcc42c8ab8971f69f49fe8639374a5c14c19fd8cb4e782c3bfefdb3e9238aa6cd277178b4cca5963d197b990bf21341a6d87ff21e9e79611a8013d0be4a9cf45a1688a8ca0dee7223282ba25fdc8923ed43246faa963a4cfda66d2cbf74a7f1a7553fc5df238bc085fb12fa51022b1881b872cfa51f610a18bb0324badcc7223a3efa6fd74d8ef48e29c69d1e27329a1243bbf1412d4b3e5f742921f87ef50eca6777a1c6e2291b028faae54c2a5984d267cfb9b25d516242dbc162d1e977cecfa2dbe5289387d62245178496fe5a3bc25d1238ef19e1eb349d74c8f82710c6d32fdc8a7e33718866f2a8523a1e843b1fbbc6dd157b9527ad1634fae905ef459b290bce8479f8bc8db212693fe7efe2efc5c3249bf7826b960d626d724ff7865df0be9af36c91affd017c755a6b0c35ec99561d19742d3bda50f4da148fa6b0a4b3788166f8eabc2eaa96679ac32f5c77d1ea5cf6fbfca4816efed5715481692b7df91881e08f7df2f79f9492591f4243bf2931e877b250be4a99915f2947d936869b6a46d90a7ac6f91be37d4259d151c7d6c2b233de947bc495a7a0aff20bdcc55f6311ed163ce233d9e4e3fa636f7a2509b709af4e18f5636a2493f1249243d7f78ca7ee8914824128944d21f989fcb94b12257192b93342cc99642827af6e840eafebe1bfbabb1bf19fb7b1454697fa4ffbeef4ede6900b98751c5c9d196e22a255a20195dd999abecdbd6b62a237a4ca5467a7c59d632911eef6c5b20eee32b630a488eafe8788fafcc20977d218af86a6a868f35f60d999df0b91d12498b7ed4cd3689445ae2c7262d3123f2c0123b3f491c296c8ff2e3709107381eb7d1f194bdaf6d85a48167933ee727d1469f479f45b93fd183388fcdcb7ee2051addfcd6494e72d24f699ff452b22491daf9af0e0c49df9dac2f8f8a8acca3c7dc2a2a32e9555364d106d999a75453e429dda905adcc53f6e90784ddd253244dd2a38f4d7afbf47371e1863c639eb23fd213e6298bbf21be97fca49f1f0ce973fe4cf29b59267d7ed1e79503912afb222c72e1863cf2a41f53db021922bf8c3efffc60f28f463fd26ee3a9941dfd85796aee91f69be9dd1ecaf1f80a2d8949dba4e75e9ee2786432e947eea583dbd11c8fa7aac8a32591c42b85a4615f7e5796a9c8a230f4d3fe52564655f6bfef4425fb1bed8fdbdff753cecfa5e7c27ce549a2f7232e8ebcfc488fc32f8faf640e2fbf7d0be42ba3b7ef228a4c05d66bf7f0ddfd954916dfd647c68378ca7e32fb36d423ce7a4c89f4f8243d9eb27dd51af2e8f35f99af98de6ea50dbf83266d65b6a4392179a479907e947fb4b2d11381c819742040b67d4f1cafceb6f6af28936debdc8be3e160272e840a3ab9bb704f1494999234a60c5d763041f61fb10e2969ccefea7409eba697f3697ce9bbdbdd87e0a5fb2e0ba92feceebbee71d709f56c19b4a5102c64bb9b1fd6638d3d6b583add4abb81262f1bd6e312fb7a7854128245cf1e6feceeeddfe856ba0fe2445755aae6775ea73b3da23af7ee15793cedeefde4a90eff14fd287982902c74772f84afd015d4a9eb5ea2741b1c94a02b9237bdf792ae5a889e449c5245bf8528a5ca24be5adbcf1a9d9dedede71fd9e654a287a7b6efaf38ff1375dc9fb72ad1c357c0df7e4a215799aab63f59c186bdfd9c18dfefee8828ebe9b46d23dfcf5ab789bd128a678b5c6475441a29995a909c4a5c98a04e5ec0e8444cbd6e2a3352be92dfbdef78f8538feb5483baa8ca529f9962696ca650576cb6842f9c631bd3e9e1fc90899134ec8b669a9029e1cbe6bd146cfbe1ebc66eecfef86a860cef7910bd1bcfc6532c57d9dfd9f5f470c258cd0973f83094f161178bc56221ca875dad16c6f8b073b95cae507fd8f9f884e2875d2b0cc3f03fec727242181f8a6ab55a2d7cf16147139e7c288ac562b110f5a188460b4d3e14b95c2e57e8e243918f4f58f2a1a815866178fa50949313927cc8d56ab55ad8e243114d189a3ee462b1582c2c7dc8d168e1c8879ccbe57285a40f399f99627dc2d1875cab15b6c2300cb99c10c762b1182dc4b4307ff84af80af7e12721593a97cb153e11bec22eaab23e21f6095bad10b7c2ef43fa614e580b6b61f8229c1386b7168698260c657c2e610e1e2b26430d394489d562b1586ddb176380ba73f974aeced5b97cb6fd17bfef5a5d4ed7ea5a5dabcbd9f661c0f05ed43a9a5aadd6d16cfb2f4e3a548c168bc568dbfe8989c885c8e5e372b97cb67d54097712b54439a1a82512b540512b67db3721c12db89a8826ac7d4d44b3edbb30dd1217e3685c4cc4c562b46dbf646423712ece8773712ecec5f96cfba7911ab2dde3ede15a5c0ed7e25a5c8bcbd9e1cf5318867a04676662b4582c46dbf673f71c49d7752eec835dd8855d58a4ffb5a0b88573700bb7700be798e8cdf4c6668afd9c69178a1e8e77e3c978319fe8b1240def47ac665b9a0b866d3f6b218fb8866b78c8573bbc17e3a97ae3ab2be3c70a64e7f063cdd1e1c7aab3c1af36be927af06b0e64cba5a1fc5877b0c1af359225c6831707a7cae0d4199cda1af29567836cb93c3c6ea84916fde3ab2b0314448bf98ac805d9726f6ebce0235960b87cb5cd66af19cf0cb641d4834f8f205b6e0c4dc2069f06912c2f1e7c4ae3ab8d8787d6f0d01b1e201b3c79f0690b64cb06043efd21594c1edc70706a3894854365827ce564856cd97676d030932c2e7a7cb5c9f8c8fc006db044886cd96e6e9ed8912c2438beb2b35990990b7cbff195d283ef3c902d5b8cfb6083ef3692a5c583ef31beb23cfe830634bd41b65820a0380c49961199af2c0ece0c470807247d41b6d89d9d30c424cb48c757564666a7070c1ffca904d9626f668abd59c2067fe64896fc609dd980a297c1067fc240b6d8981ebeaa3c433c3f36f81c902d1568ce4896f9e02bf1494816fa3319f844f8aae2509505ff45b6d49d9a6fc8803fc3575546882024cb7d70d461830f82ff8974cb902df50645b2d4071ffb8acec09fb2a5c6cc14fbe0834ff2b980af9dbab14bd341df580efac664e81b43d1b46d3f86f6589286500eff5e9efbf7da1fd1a078effd7b2f09c687daee6cdbeee88577b26d1b6adbb66cd29db60de7ed872e44259bcc76b3c99c36994d66bbd9f64112ae34db6266a5d937db62b6fdaf0536f100813c251e1ea06ddf1bb9249c1d50e3e0e0ec6cfbdd680bad8cbd397d5a5b999b6d5f94ada8ce6c4c9dcdeaccc66cfb9c50ae7bbc3d95078887870768db8e9b60ccb6600cce0e0e0ececef630b5b52a7373b3ed5fb13e768e75ab1ebf201da6b86d17bf999783fc9362e8a37d6690343efd61913dfab3edb33c96cc27fb803cf5cd5c651f646dfb557ba63f5446817cc5ff0baa619a5bbb354c5382431a0f5bbedae10b029a79e29c214e208a9fcee80ad7688d992dddd04cb1a7d9ac1baaa23f16bfa0981a5d7932ddd06cf1989029f6eddb6c5bc3355c63f14ed0c161db17698f8545ae3f86af6da594327c85afdbe32be327bb3e305f113fd907e42bd5dbff823ea18f663775d1d527a32a9fd992678a7d8f4555f63f99cb457d30567df75199dadec52b535b2755dbdbcfdb290ddb6e3f7eb36db7ace3b7bbd83c6f3e6db11fe64104634490c5a2ab4fe62afb18e7ac1a924727547fff3e28e32bd22795d882f47178dd9cb8833863aeb2e30c9578da7c7e9f8cae4aaeb2bf7d324cbd185fd116cb636dfbb44529b5d976f5017db24d7f9f6cdb10c53f6004dbd994b4a8cf6cfb5352fab26efa9b68777d5acceea8376772e549957d4f0865e50cf9b2fe4cd9049223fc2315a24292657a2c5f494969b8ced8a16d1f6ff473c12a9bca5cc4d0509a8cae68cd55d635e76d9ecb749c97ca986e425bfd8dd26476a7a990a7ec27575488aa244b7d5b656c5b59dbfe78b2ad1e4d355321ba7299a9b27f3a653a5542dbbee873a12a1e15df65660b0ba4bf40aec0b73fbaf763b8fa7e24b93fc2d8a3cb5c4965ea3e15bffb9b785da4dbb66defe27e2e147fdb06fb600005c8dafe35b54e204f39cdcad7d0ec098477d553e62958ae349bac523b6542d8a9aebae5ab4fc8a3f115ead1c83c9aedff09d1d504927d42734e4a29958fe5ea4a157d2953b445ca14fa72d6cdc3be9a405b86ab3f0659170b3270823f3f0bd996506c298448cc6611f1d979e3f0c1f041307cccb1c207b9c7e1639b1393d147f783a73ceff3bcd42aff49d25545f9c2ede334ce3d458fb979bc4099b870311a9d484840fca3cee7488f73dbc0cf71a33f7d2e23ee39d28fa3e7bec3a27d6e64c2719fda233fea5137b6a19a22dfa72fbd1de1c6df71e2b6bb18e00445df330cc31b8217bca0078ae38c5d5176f51cd75aeb0c7cd3e7020261f70d4fd11fdd67530f63fe2359f2d3eda11e02fc30d426398723df0bf859db0f47ce61bec2037cfadde8db31c32142b9f3f7c23d06c1c7bfc1ef72ad2fe9f79dcea3fc21e638f0479f71f860f86016c110673087a3cf8be18122f654178abe41719cdf0e70e39770e32ab7efb10499eefcddf7b998ec486dd086a7c0ef429148be26f96ffed40643ab6d4c9a3f4fd2a83736e9e9ffe02b3c463724cbc8d327806409458a142952a4489122458a142952a4489122458a14e9428af2f7af7e51fcff3f397914ea4b4afe747a93e94ba51f8d3ee7ffbef7bce7b8c7f86b7d6bff8482ea019d99b2e5dacc14c7d766b6dcd74ca12f9bd985235235c56002d36162199e94524ae1cafbf2c7b0d76cc133259423323549a48a5e2230aa0e426a3afb0af98aaf4821b59c4d1ffbdc19fd1be42bf3d236a5bb7ef55545f258833280810b5c0ba02051010a8813d0474820020f8118463cc001270d78510403168052000c2260125032840b217620e0f46212840e204c2e2d7e38408ed226c16180028c0840bad1c29207306263b5f20920003ed4e801f442951400d0d88187199c08ab523ae420a34b65000317b0001215a0c0048e9040042060c4031cd0802218b00005100193802184d8818097207400e1f2c301726c1c062800016eb4b00cc0c66a4500357c08400f2a2900a03183871d54291d72908172efbdf7defb1206e55e198ff23250f4bd3d28e2dcb7075685992c7605193278304c76effd7b77f860aefefbfae25420f50775a7ead49ceac4dd28aafbe2df981819266c5a33271045968e8d8d8d4e4a260312031da20591e1ffe785e83b860e3a3a2f26c4603c4d383971e5d06ad5cc2007c84d0d50281a17a26f183fafd7eb47068d5653c38c0c6b484989908e8d8d8d0e0a0ce60385073990ff44719abf4d6200011224083c2fd7104c261c2d23f3a3899b1a9a14f826f979bd5e3f2210d04c0a564dc80ca3914cc7c6c646e75faf1d27cc1f9f1e28f81e81515363630357101c1c7c5feb454d0e4922343f626411dfe1898f4f2c0b4233d916380e86fa603056d9546ad0e8d9b99182b19eb1f9be912b1b18f8b102b9c08f35c7022ebe178184ebf7af2cb932a2023f56190afc586726e0dee308f7a73f72150209fc488122f0230d82c08f94b6fda94f1a23fc854bae66eb013fd297037ea43c0d709f3945b83fa591abe962c08fb466013fd21b0578c9fbf421c25dfc6ac6607ef45a027ea4ac21dc274d08ef91ab59dbf1a3fb20e047ff79f9d1819c26086f812357ded2f1a30701e24777b978e9dd737e70b972d7017ef41f397e749aeda377f7c1f1e38cc3906421c9e4ca6306f871ce0af0e31422c08fb3e6b41bae23575e6bf971eeb0fc387b06f093d2cc146ac37fe6cc965cd1d6eac769b3f2e3c411807b5fc3694e0f49bf871fe550007e9c31db0795e7c08c1249481651ca8f7206801fa58cc61321573446557406a5cd14faaa1fe5ce0e3fdee0e1c71adb7f865cd11a152208c9d2a57ed461eb7092e17f5b2892657b9f5155f597a7cfc56d500c5cc00245cc960b84841e7d1b518118056213d03d66cb951da147df21a0ab8b25a0471c010d014d335bee8f1134d7e6012d07b41ad0ca992d57a7083dfa6531c0b50097025c3eb3e5d610a147df9b0c269680d810b14d7fd266cb3624841e7dcf1a7dbc438f1801fa45d3ed27089a165d6d363a5a40b45c5a39b365d3f921c745571beb007ac4395cdbe5335bb61a1c7af4ed316a8058016204a0efb4d9626f68daa696d2cc166b69ce6cb12d5e63f1da00bc668306aff48857f49805a06be8d1a965d99a99427f556554459b2d7568a6d0f7c11500570f2e159da2c75306801e338dd80c5da3abfa6acd96fa33536674556d6a664bd59929f479a0b51d744da56ba9d6d6418fa79c831eb30c8da26b36fd0ec380660badc160305a031245511445d145930f468b22caeb47d17a9c5bd4a228c2b438b708f3a1352882807ea2a06d4041b9328cf195c5f8ffff7f51145f14ff258cf81fe3c58f31f7ebff0712439cfbc51aa406a1f2d457750d4114f5fcad5f3faf9f1727272727272727ffffff27251fcc9f9c88ff2ffec9c99f9c9cbc10e73e11eb8fda44bda93595a6a6e0ffda5c9d6b737560a05028140a853a3939f99313d4e9833941a1fe4ffe4f50a847a1504030c4b9514033292aab2664869313190cd1f70bd1777759b7e6b22eebb26e8d8b92929292929212140af5285409c907832a293979d49fa0f43877494949c9cb853877894877a8137e7c7a28145028d9904c261b32399d4ea7d3e9545252f22525a7161f4cc9e984fa924795e871eed3e9743211e73e89d486dac0150487e2a0a464fa76f1fa79bd5e3f2d4c2693c964329d4ea73f9d4ca60fe6643295fce94b4e26d39b4ca6dadca6da10111afa83c614399da68d8e8d8d8d0e49a9542a954a2593c9f42653a9f4c1984aa5d39bfe64d2a5920f893877c9279605a1996c0b26d3f4dd42f4dd6dacad66636dacad86341a8d46a3d1a8542a7da9341af9604aa391e94b6f9a7ba447a3519091e844e87123944ab221994c36349273ce39e7d16834caa40f669473e9475f1ae971e6bcb3e833ae021cb7f196b360349abe49af9fd7ebf5037edff77ddf9773fe9cbfd10793bf6ff4f947f9fbbed92c28668836abc8d346c7c6c64627f43ccff33ceffb3e2f7f309fe7e5ff3e7f7a9cdbd39eb7138a737b3b3c5190c56053f8be97efceb26c8d65599665d91a11c7711cc7719ee7795cf8c1781cf7bdf79fa7c7c971a29983a933732690b903cf930dc964b2a10e638c31c61cc761f083e130f69e7b6f623c8487268b0dad393365dcc071a26fd1ebe7f57afd6cb5d65a6bc5183fc6f5fb6070ad1cd6e3ac75933fb420202f601bbaba33aad2992d1876b271faddb5d65a6b6dadb5da971f4cd5d87a7daca7b5356c0d1b3decf92c1e211f54248de93c78281975671ed60256c1f80583a5f09cd30cfe1f8cd5d84fb28b864d3f6791fde79dc15058b54df1d542913ca6ba19de09068351159517dbc8af13cfc3eeac05db3d5777e62ada220d2053f3318cae0e802d4c0c9b5e21b6698a575dd6b0b1e7ab8adc59a53eedd76d351764cb183e2e3067177c86ac8108112244881021428408112243860c1942a3d168341a8d46a3d16843860c193264884f11a09d3d45b1276950e92915aa427555f124bc196eff5757bff3177b437d37fd0d377b625fc170c83aea18767d2b5be6cf4c999fb7904d80c05b8e3dee9e1aa7a8c98b1e4bfb349fead4be22ca4ffd18d5ea9734aec8b4b24e29893d2a5357a2eab6fd86dad56db5d6befb948901b627ad76bb981375de0786019dd6a844ebd90b8bdeb1a1910040002316000028140e86430291589487b29ae30314000c769e44665a34184aa3148632086210530a18430c01003020002322541a0005f4fea0970ea8de067a9ca8e7037a4fd4e3837a4ed0f3a11e27d2f381de13f57c408f13f57c50ef097a7ea8e751eab999d61a04663651cab5761a911d98f684481d2c18483e2da1e08f722e11e30381dc908b215a380d04fc3c623937dac7d46c317315e8a7bcffe1c0c443f2c16500f1508fa3feb8eb805d950086b853bec7898f427702aca8bbfc5aea5dbf9d7f178b19885000d49669b9522cd37f8e2a33644123265492560f6b552e52df7c2d626c69703556a5e2205098a4548490f8cd22fe46370aad1854a916de56563731707c51b6200db0921d3e7935617f2a1b042b37fbae0ee108cbb8ef5c2bade46292bcfc96655880b5f40781d4be09cde4fe319b1913edfebd337ca8f526ec1437eb94d2fcfde41158f3e1f59df9827faa1257dea31b8d54b9cbc61ffc4a6a6c8bfe9244a5568687732a095dc807e34f3174a51fe45f279148c7f873d954d31476ce79f49a073a08e1941ae4517633e2c0a974b2937740d95447b77198537e5b038cc39df25f19e08b19feb7d4b34c6dfd4a79c794d7e63f89670cd8b6fd93bf8a816f935fd99798596dfd9779c620d7f65f92770c986dfc937e8a81df867f898f28d2f6a98fec2a2299a849d17186ad36db8a509ff47e2ec3e054969b1204ef0413346884fbe10f02d0deaf7bbc7d2f252899465a95a492b4694b09cad390462554927e5a2945d934d2aa2495a44f4b4a519a4eda2a6d7261913556acb2c09a15930b96acb1c49205d65931b9b1c8120b96ac618dac813288aa14de42db4a8f859c2944be8a84f83298ac0c42656ee0d6bed4192418841ad6b1658accaade00124fcaa071f09033ff44ce2c2b72dc667fb11d824ed8994d584c1ee56ee442432579dc7b12b3af9b013607dbd99ef4a0587824f10cbcc6b6d14c8aed1c38ebafe33e3e0d9c01a475de50265706c9250171c7a8514ed0bb0b376729850ab9108e00b345a15bf0dea4f9adade404fd2d681d07c617e01f44aef60963a771f409b1df58127250dbb5987f6972c551c9c45706f58c0a7c39c728932b77e2f62cd5d03a2ec4219813887ce50940f19541d92411772c3ae524fd1bbaa506cc053431fac528e49648293de142ca98815d3289ee0aaa8e037108e70722577d00b93bc56530ba49b1ae925d66d93521379499908bc523352821bf580677181168490751932b37e5e6591aa13a4e94279017984cf521c0eb97c1394910e1340a8549ba0bfa951acc85856c0b1321478b47360d7d1e0ebbd404ba3b281d07c021c81f88acf20124771a97c1689362d3869b90323474d431e72229547fd866659090a3cb23ab8d29689d5d7202dd1d341d07e210e40f4656fb00534f3050048ebb319b9d2609b916c62c5fa5167c49a65b06b5b700ab24283333837d7b964aed305be21f9b62d4385aa1e012f9063411729d798becca9059a86f132e4ddc81ab88f412210f8cac30483546c51ed405fdc07d467a73b98c04ec141b3686b543171e5a08ae1bc9a7a24370479d877e746af16404863692b766979c96f28045ad6d1be5cbdea5f94e825b46f25125b86130e034fa8e07226f4a871db7819a80de47e7f6bc546004a2b938faea638682781bb67ca360b46ac6096de2851e3775bce74bd845636a7c7543501aefe1acc0a5fd4181af4014c689572cf088128871d991d47a85eca07e6583b640b7aeb41c30396d39ae80c464b468e8da5e908224bfab60bab16903bd7ede16a93e7706582b31e0d253b7c946045ebd47fc3bcf02ae6c23dfd4bc4461b24856e830526e3b2d792386f9554b1f289c1811be115fe7d24ce3b5e1feda5a0e4eb93fe3e85d58d13f0823cc27c11276b941cd8817e2d7c4a0920ea61e7b55900dbdd4c45c028c789a45bdb12626999674a526adc79c9e0fca63babcfc85bf01abaa6d6ed1ab927b813d6df3ac7d880600051284e5ea2be68b9a8c4891befdfa7d14a62260d69875f4f9ac304f27b07ba5890500eae10c68e77eb259992a72d81ac7eade64002547db57aaa7c50fa9a80535002802beb5476daa82a69dbd7a5007445e8b0d2bcc2027d193f27227622697c64a38dc7136fcd5552bb0cf8f0f6ffe20077e9a984865ab7a2efc294258402b52c061cc40e0d1747e9021426003b0314714a90f88749e3b5de9a1fba3e2d69dd7c07e2f076b6c6dc18d675d40b6479dc581b3d2c32044824e9859751b8a75413090a3977db56df2232c773157675400a71d0d5a253b35f6b661ae4a2d4505e41c2ebbbbd58b9f2ce43526645ee0764dcb364de36d032530fb5e73062899d6cf0723b303ba2ca71864dbac434264b421a44d89e270d53aacf48f8036fdcdd0e99dc7ee9f55dee8ea36b99045ed2ccb6a27e649b991a09e60b44023c5f6cf5977a58af7c21a0f2f1d6215bc57a375937e77bf56de784fca5fd5b3b9b5aa7cbdc2b9d18df5eb35dfdc97607b9d9ec09ab846a0322d6168cdf8d08fe1a00964d53ae2333163455fb9f976a15d10f1a2e167b512b0187445f0f1aa7b7c132d03ad0cba2f6ed2fe4019190cf75e9045ec7909fa4f822e8dc0cd4911318f0d586f0795d8f92481d7426cc3d6a053d2ed44f479477e4b1a3eccbcdd471679bf95a96c9236d17fc53b3629738404dec089e3d38923b045452b8c98bc113c00c36211fca74889cf82fc8a2482df053ea190ea81c6d889a498bc5f987201eec5d12443252e1b30a3097016f16f7d08c4bd00f516df6fc8cb0066e5ad0c855ce616ad824b6cb579a819c33c2a5d627a2a88807e362f472e736b14a5651aa7c0e728a41bec0099a993f067c04b25c0e78b15a17773d4fd25c299be282003c2a2cae8c97554162f60a16640cbafc3ac631f2bcd5902f9e2215701a2e0eb00e17305a4e84550530e8445605894827e6125c1f141816fa24ba51569a14716b06762be67a14986255f6d62d610158a8445ffa14a8938179d9483aa05e2c8937827e91f94aa0c0e0bd45160f367636d7484b4971decc28b22857333531d9644b299c245991242415988b52f0ac15507be9cb3e8b3ae45011114565d505faca1b258034b35011b7e1df256132c855ed8ebdf441d359478f499331397164502a5641e329d0358d229fe576f027008047b27df1ff46a09ee0b7011e630a548dd9f74fd2c2c4c9b71b90acc951e0c1723ff0642fd637083c54fe93305e2997aec5c20bdac0b0984f06eeb52f8d32e99c3064326868fd597c89c58fa5d5e4dd0b02818e0f5c570eaf0302e45417007e3f411ed2c0489969e49b3863e0e641a8594b1012d751cf7aa3f22208281bd557216c04c932099e1a300ef02f4f88937f584f894fa9afbbb1a6f7521025b06589d6cae40831ccc99c085166962285fe9ab380ad12a27de216ab41c7aa10926a704bd548fdc439218168089a1ef6954a23ea54e3f0f07a6e0b554ff472109522a73b1fc665aa587744ac70109094ca5904cddde1834f0e59ca34ff52602a760b07682c2f45be6f2ebdc0153f15d74bb9862d9e42538369603a50a99d5df2054ee022e53a6846d787d238017470c6d7a62600084dda6f6cb26c648816470890098508df2646e98905a77b85170cc04e29c3638e84c2da9ae42d3d2d827446f66bd9a012fa3201a6ca0e54e477e754f06c900b077557b13f415de9cdfb843ba18f56e0000be051793f7f084112c923ffd42aa60260e1b8ce1605d0cff41d4cc2350f551a38d8cca0e33d0b28ee2bffa36008620d85642fe41b8ca60dba10eba6042f9ad4ce74879e5c2c84d6e8969c5d368ba0016df825f5b0ca0e50fdf8637a222b4cffe6308f08fa1213eb0b5e169595cc89b9cfc81b080c16e32f1819ed265c80257dcf7954311e2e90774a03663397bb9b30b67b10012a84ad4ca21612b651a114f3a63a98be931ae47be0c8e7877c96db326c49ae8c941b1079e9400e124a68c4e45b15a1a61c687837495e92801177a018ea0d05c2de9bd36d82526df041f4e88e62736b8d1d0a075dc6a9bb05a9057b4e30064b03f7296595b2cc94c734f32754c5393de437ac989272aeb32a39befe415094a3921e58a8d52bc8ede725f6ba28434579ae164309c20aaa2d9883cabeca66b30c34fcb4952387576458145b19ed3085f8d901a921bd40c21e660066d556a8c4c57c602443c95cb52bb8d81c80dfbffa03cf08f1c378a3f524c5e939e47d7aeb210826e4ce399d891c906158bc62218456f1c5bc8154b495ccaed9617a25b0f226d2094328f4a3608028067cab832cb281fe9181c28e47e922a703510528d9a0433a07b75e75e0e31277c95df92cc12815273001dcf2c805346f298f996f2761c19440a01f9d4ab28a2e821b78021776a58501b5618b9c3b630861636c9651eee838462f8afc67cb76ce5929339110f991e5806bd37ac1a8528f92460f5787559b006379ae256e73cd96983059c8fe76ba4c81ceec05ab3bafa7b996a13202a7bfd01487f989d0b63efe38d6e7a9984ea46c614c6468a2d8819195b20363276c1d88898c9d8207483a31b1ddf28921b8ed3ae606c44ccc2d84831053122630ac446c62e181b113b191b4237707423e21bd2b831c1de0a5caf69bafc4fccfd4d91d8613f61e4b14b95a756b109536af81523063eb75cd80766c20521a1bc5f8974b316c399dba2f9e35f6e78177b3c8af2c0731b7a2b153847832f1bf446fe539ed201a5019cc0f4ce01a80772e9c06986490257b0210361d06d2fcecc88bd2faaf6cd78dd363a5cacd3a23a9bb139e2c728853f4eb359e869ed64022e3dc2597ef761f128cfe9bb77dacb030144f0e738dc627dcfb084e032b12f9807c3896f2292da38dc93536d6118b2b4da74fe44d46d9df722870bb1be45bf860e54ef04656b890074c06166b0d9852ea382c2a4a5ef3db67409b3c238f7959e17612f64326208be81ddb9be2c93a6087b29b85b2d202cf8c5fb721a52ea0b9fe14a68d66796860215187db685f8fa2e54e5586c300fb1ef24b796ef546e6e89928dca3d355a2eabc12320887fef64c6037b6ad61c8e47edbf1eaa8bfd6611c6f976d860f0a356371e6ae2aa2fbb1212f5a011033352b9ac7ba0791ca394b952a0b20dca8d36db287d36300cbba20e449ce9d1d2520a01a0d151c17dc0ea225019b82e35fd99febdf253f64e4c6c074962b4da8bd87e123ff14d4dc87fe9857d7e5f05ad18ccf0d31e94ae8a3e55426d0a4ba7b0c613c07d108c0184a31999eb33e11dc120c934b053942fc622252e1aafbe81c15de0ba99ecd265175dbbe9b2cb2e5d76d58dbbd4d08ef25a1bf986d5b871c61e337e2ce3c71c63ec783a9ed9783ace31e5c2844623a11ccdad53a579d0f5bc5a0c13c03167680118e935881bb86ff7f9491015e85e27a8f550b9bcea5d3df7c424a2698db71e1bcd78f6b25e7660e1fb5cf6696756065e9870628fd0f391acbb3d1681708f2ae90076304448fd8134f33959bc6083fc75935bdb07353de717c85a5636cf19deed64258c2ae5d91a60e504b034ade8d668dc0cb28c82d0cb66520a6abf9adb5255907394c16c5f229a3cfd4bd8be009aedc3ad8b4fa69750a668004a683d50a75c2b6bd54438e64c30682eced33589f56b8cb660d75c68a20863d74c4d974dca4eb4a723808b4939ae9000030c187d426773a2a3c09fccda109a0535905cfb0a32954befd088807251958a62ab4e76f77f39975eb33725fae32f1234b3490ca632a25f3ce927fde89f698a12a66cdabfbab3085302fd579efc137ee4cf7b4482299be65f9ce469cb702b6c26914f70b8efe1ffac2e274c15f3f7eddf11a66cf37fb1f77f52e5f7f70f61ca9e7951b9b9a86d3d4c0992dff4e14fffb1bf0c1217a6eccc7f91b002a604c96f7ce0a7fec85fc4c70f5376e6bfa46965a1b1a2a83bc2e7301edf2402f8230a98b269ffe2c7224c09b4bf78d24ffc913f6b354959cbb861bb3715a632c96ff8e0a7ffd15fc489075376f67fa958075382e4b77cf8d37ff42f62c414a6ecec5c42b3075342d9f29b7e0cc3d4621785c5da51277ac2542e4857eac321839d7a09502e215121dd38e36b1dd6970dc25f42341e30b5acda55225afd8ed8106798fa40002702830f960a7bc054d6637f660b3e4c7d78f142bcc72562f64b18e0c8b8a6f330555e3ba7c1d4cf213eadd5886e717849b7bc78ef350266090f2de6ad39a11ea65666d741a117855243101be632aef96996b2c558ba352596d21c8513bf295ce977c8b5b7bf00fe53dd9585d9f728cee298329819807c0f1559373b0b6627b739d9eafc81d1614f27f8d00976474c1aaa9fcf69872c0642e0a91876033f5a73ec2f1e93d36df9566d292cfe66b56f831430d8af2a07b5cc4cccf8188c86725fe9096cae33461cae87b2adeb8d4812cdad45d2840b79293ee146910a52d49004b8ec89c594e2387d22e0b73891dcb4ac35d254553b8a5528ae0f10e2ce5434570a36150f6b688ace0e7181db85e6c3acebeaf5d902b3e2372b3df217e8479dbadc22cda6e514c75597e79690347c3e910f0aac628aca9472d662e496d8e7eb4120b1426bde1d637f28a2bc47f76fac9a6455cfb02aada7104027827a5262670059c88785973e84409f81226904218082384f2e829ac39ee3592f29148a6960c3427763bf19eb844a23070666e63a165b46585ffe607f46a106d6ce4b3fdca7d33cd25292088316349f80bc1301a7f0244e84bc8a568115ee367f372b40aa5305a4f8571132b5f84390e11fe96278630e0158c09e976db58a867ba8c9be41971415ff6e8114b4c1c80c69ab45a1d4d1eed3330b4d066e1555996cfe7290802334fc177ca06410887e75530a716db0fe92f61709208edfef28280f5bf32442312e580d8cdfe27d4b23b26b0f2d1636f63973a82418fc0e5a1208dedb7821fffbefe67a4953c9f9a749277f80d98ec180f8d18afeee4f08c5f560eb1a3feb1bc8d93f9fee043b85728e974cd19cbda732cec9f16f0ab6af056489c76ac90fc33d4918ea4e62fb6da3e149cb74819b1bf7fe1c9a76e479886b5ab12630c8a01d04bc4b10b9cf2063355488227a9b14212b00419ab0930f48099a26990968c97e80c0e41bab7b2444a0680bc2a5f03f10adf7e88e1991800e64321a6af489f2a7ca749615a67011c8fa73462f5044d4afa8889e1970ce1a080f3fe48f1b587744f4547acf2746da045f86a59e8f30fd1ec5f05bf659f3d0fb63c28d0e679089d33f3a92d34fa6266a17aefd8743626d9315370a1528e29e9598ed4a44207718a0cf6f26b6140ca47164cf862505d94b0518177c854e9745045b940531f81f4d317a706521c023a9fe61b36ce87bc015d7c18a2b586f2200a9190ee277b25be85b24c705f3141e3cc99bdd6546049952e4f9583cdf21e60f410342debf5426975cffe3c39aad255d8cecd6f2ba61d8127ea09f1147ae418c51f86c9eb469c5488970bb43335594e0938820f2eb4d5a73c0fc4badd92fcb40eaa657d5780a17b2d6274930dc342a98c9542349ebda10a36ef0b6157869e468e40ac5c5a8d942852d548910d48f657385234181dd8b88b344e391d3e34f737afa91563ccf91c776a3634262642ce737861b32386982e61aaea1056f88e6cc76433d07cc5c8f9a1b1434c4e0052eb37938ba5d4c867045200931f6014cbdd561b2c4a0ad0f23ce6570d9feed870bbf25a3d181f09d69043125dcb7c9a69c94b6b74d43adce1d48d276cc8891d024919f081aa33279a0efb033323d58ede85968985135024b8b895c57570c49691e06e64d70fc4a423eeba96ffefba7381cce9db1b072f1385e638d57933feae782bc210b75b76ba8ac291e15e33a3994b5de3b478a0141989e2d35754bb0c24b3ff2d2d9486e6692391e6000df476e6ed2f5baa20b214115b2b883add411a8aeef7cca07e82290febd170a977a9e01f1d642fdf22596924162c6ef9cc3a043d43462648b1ee13c272738aea5fa0d453a5c09cef8b8c677ac9d2d9097fe592e408742f1772102d2956bc1548faa6ec0316900a5fd929882095e07f0a498eaa30a83c3f77f8941628cdc26140e500896cb41dbfe85a023daf5ad8e0316df9ccb70b3a1915cf731860536ca5b1e49c1d93dc281b17097afe87ffc8028d7a63a8be813f9665bb45e6c246d362dfdc67c06836b1cc5b791ab7b930c27b67ca079632e94fb69dc86e46c04731dda40e8f5ea859c3e334c58fd20b304632da58abd6682f9e7a0e3d99f0f2659f76a465d98ed30966134cc70065c62b16beb0e9c01829cefc8901f34b4b40f98176d063be08ed0f55a69fd39fa84f271680c3d722da3b57f9339370806442f9bd8adc2494f8df73c91eaebca9a14fce8a13b727da5b8f42f9109c95923699ff931d9486a999f7725422755b04e715a28d8673c26d42c3528c535f9609cb46a82d082fbcc93bb9f2a8bf9c4e3b3317d8e46e04acce846e4be815f5769d88ccdc4c18658dcd519f4ed0d3be28791a93281d308f156b50aeca73134c8b2d9c4e5f759730e5d3716e25c7b343ce028d5c1e2b37e3dd11e82aa17c7a10ceb03113efc854e6e0c1216a4d01ba21093eae7123eeda42cd790d0b446a4c123018d09f11e60ddeaa85223a59d58c50ee78b2862f2c65fbe516873d32649332ecc40eaed9c2f0f69d66de8a5be847d53f1a2cd33c34fd97018a8385cae10fd9006e166c98b1bc820f30629e236b8f014475ee9c1a2b6f8a19028cdb83a4b067e35c201dfc9fcd07b8308ab2afbd77cd7ebfb65306e084af515dd3c76484c10cd5a085d3cd0b56424e45c41714fd483579aebb441d08c733f45009f4867e8efb63ac37a5976cc4f4f5d7559233b2abb3b9e884d7047c9404cbb85c974c936e2084d0d44fe809e05180af52bab1dc428af43e44252d373871907258e4f38e548ace53112c9d98abca439ca8370f5ed37d9e0cb97e7318436db60f0faa3d23d0a74b3c1b45fe969eb64c07d18abb786438c87209ad87947fe0142855c9c80a7168ba79bfae1d4dbe3f71e1ba492f1ec4f33e6083f3e6c74b0ad9bc0bc6979e94fa74040093f329709f35838b0dfe88262fc7ae6350ca8f9a73d5fe4c75883aa0aad33d50ed1be6897bd1840f0cdccda00ddec32fc73e44bed61282dd14accf3a984b17257611e497ca921f0d215cd39928cb05de34a4fe1a5f6aa92cde88966d9802d54930b158a96fee900f75de306f4985658b3317b115cad6297c74cd838b58a93715280fa9fbeeaaf05a84d134ff307d1b13f9dd090c3a08615ae29289ecabed8c6fbb57f5bc36e14e3f46ea1156b9da9d6951dae38d65a3f3aea2808ef8677e483a45dfb4b6a165bdca6f92cfc545674a6101879df338c3cf4e779f93679fac4501246314cab89cd1e4032190a24349152b9b2c25487a95236f9a5a73d4790ee5f1123850b37eefb032f7b990a8c352ef1592de857a7ef743f16f6abade82eab248b3fd578bc75647e41446ac41c40ccf46074d10d6a8cf882e3a40b92a0c2e97d2de4229cecb628715f7b9c0a3b24b251c9d357c0216e07e76a1a1d6d1a3e68c891d64f3430f7591c6e9941f09ce589a3745f4a2b20ee7af4039143378124c61d2bf558350e24049e6c65a9e703e54360166298e9b0142506aecf475ac9902f6815e0baf9f7241b514746a1d06ec8145a9f37f64e1e177a5212955fbd7160c6e6ae70f6eb6d113198aebc1cfe1fc65abe593a469aa02c41ba23a8f4ca3fa220411821207cc02734f0ae72ddc2976427780084f6a283286c52ba3eebfe23266bc1bb6930e7df42fd58a1b6f5d24366375e099f178ec3169921e93b1e9e4b051706d0262473955648ade2c00b939106d816bd17a281289dd9f5e952816e80d824de4ba9ab3dce3f7cd88587c492b810dff9b8c5930911c974a88a9237c63a836920def5e045ad19d783cf6f128fd0ea0f6b3da89816406ca1f34930c6e086553727dd6ce917ce7f1d2ef8749f1d0a03b31016a4181981453b0a15785ece90dc8e25b62c5588f9e068a90b1c9aa03f3b5c0dcd7bf67d109b464afd9e5b62928e3b95ec18dfaa412f40b83c25668a7e101b384fe2bad2b0e742aaf5d63450772fa12ad9bff51eeca350c8ecf823135587790215372ba333efa907edeed72c6e5d9061ad14c9bb914d2a8260c1b5d7e1785b7afea5143c4fc6ca3b25258812b102413401fa9d0929c7a6f9ce4c06c8d68856dc76893a5ac743aa645000eedac4daecd658f8c50229c5f8396f7918afe40a49a3ed22598d5ba242494671166f8ad9249c79a783e58e13ad11b0add3568fe6586c13e9b96e2e66aca71e01d16e8bfc423c9aab6b37199af2fca87c1106316cf35d1702b872b0287a6ea80479bcc7560e0a11ba8ed57dc0802273eef743ae92ee5255c98890a551c755b78de932db4640bd08125a5dd0b77880a04b80acbccca4882bf4276b83f2aedbc6c774b014c1b5191d6adf122a2922122431c8eefef545ea6b86420b700aff81b5aa2878429f6e8ea4d94a862c846fcce523ba7e005a0c2888d05184360ed9547e7d4bf7d8e69f0d05667ae84304654b03e765377fec91dc9a41a299f52456bf8a3fed341ac40ecbc8e58f781b8d41bcf126866140ba6a127b3fa0348e858e3b719e06008802ceec47d8df2a3d40b7b80af81a2a60f05da3435e3498bb39852375bb61d1d8f3a1f54b0b67674e8c83f9419a1c75741d87fa602d0e9a8b03a143a4f95c33f07bfbccde80db3d7b5eb2473016c88cc343b44b0cdcf05f63b2648a692b9e7ec518a2eac0cefe9690a78a38f7878e03fd0b6de1702d0eb73fe17bfab01f5c4f2d251cfd62f21729eb76fbf7c3a1fee91f637dc83a03500456bd03c501d73ce56414942fd5ba348de0ac38b9a123eee50c8dc9f8a5596a1d122b62430b0cced7e57548a2e3d64e2a1d68e261a9e118d5ea6a2dcea2e96b6b586763e383e02a64df6ca6526ff76a2101316aa479cbaf4081023e8f1ead510213dba206daa723d0ecc29f6f4a184be0168552ca36cfd46b14637a836c25025122d31a71d25753a6f0180e259dd5ec520d89942962a01d7aa5ee7ffcb784fdafc759740ca1351420f2153d6b3f012381febe6d47f836f97986e017123d31b1e007305a0f3d9b538c609be274bcb5df008cda2cee4c21a8a6f5349ccd8667c9d5964042329bb42622dbe5319704bf1be48db5f8a690b8a6d2bf7cac1a835b4bd578c6addfbe60e89e9ff798bbfc3ee7bd918546937aaa1aa12124d3fe43239cf7941f86add97371f48ddb45eef27b8fb59f21b734301377fea5dec71a71d00b92edc690c3c7da6760fdd0880111a0ce145e75aea84092a3034fb3555d21c1866155779c4da3512d58238e4431193ee762125a0b4cee50eaca5fc298b4bda65d06cd4271dfe35f2cf5ef0b66a1c661c616f868cd06ff6bd0ca9e676b7bc3e5e68d04dd619fe7cff2c6387beee0e0ab5f3f562a29e6a947ebbcc208c95f52dd4d78d0d3f7ed4521dada0030d3aa9b431482d894124087d652e979cef383b64adb377d5a19521bf130dc905e17e281b17d6d7c2ee30bad36913927fea135092d8edcca67a2059ce58898615854224a0d41dd98e576a55b471f258a0d6b05c9ad6b0c240a54f1496d7129b739450b59badec872a1a0b75af868ea792e8d16f66c6c07e33f13fabd34efa8316ae57be3781265115ba0db81deb129a5df871e23ff3ff8983817a3a80abaf26a0dab347cb6c721ae84d53677415eb8f3db5c08254735a17091e9dca1c20bcfbe0a31ad8867229d64f853321f32cab2efdd7b0a2e55e42b1cb8485d569850af92ffd5451dd140082748f0b9a59d130fdd839332384a307668d5a59e4cd1e4fb83f68266d906b770a81fb4a5cfaa1639d3367c7446dd30c803b87fd15f88333586e1541a56e8ddc9ca7051c01c1f053c4a6cb0470f6f0b199b4c1ab248d24691fdaba419cc91dd8987b5e49b03855053468ecfbcf557051b9fc7309dc0bb3c0059098839a4408da505a7f7d1b3baa3e7b29966bfeeb3cf7f5ceb644608d5d3b3229069e43edb59d79a6731f7cb0ba7a78c7fe32fcfc49e42284c40333ae68c8d2f7f877ab47fa7121adac00a013696c10361f774204f785359ebc180094a29dec63c7525e4344f615058af123d0286c76d0360246a9cbb2f138e83eeaa543b45febfed02e3f19cde37c032681720354560ea15c3bde261ddf138a4062c5d9964ec6629e8c6f3c2f8a3a04e66012a85b4fbc924d36fd3bf4479e85e300ba63463cdfc229bd647eb24589481eea25dc96c7fe45eafd622fe8b00398317e7c45e936949f6c0d59629fc557d405e3da778617b3ffc837cdb83c12a52df3f935930401d2f2d191153a06db5415702138fd2169582d763ae356ab7d7ea9b93c71eef30fe2e5d6da100695d96fbab46977b5feaba01dfd225592b6ee2ad2cd07fd7978b34cc977b812380678344df4402c54305dd0d4c7926897c30f3cd0abee79f1a87465c0e4de9f561254e93204df8e6a2d64744be91222e3558beae09f5906365596d65189930700087dba06f8d6b933b9135a7bf26e80d2260ff6d82904cfa7a1559d9835d8b4cd6cc079dfe7dc7ba464dc9ab18df3fcf6143e2103a9bc0770928cba23026be7deae0b6102ad8f2db62390107b027b4ea740e6052fd314c31bfd909756be533f9c9408d3eef99dfaf060a2214d19c25311230ecf78eafe60b061db45162d86624167dfde126639780073a63133573ac9c3977695e63a03c1bdceed27f0a6d56714aa25924a4da3fe9d2a362ed91c669ffcc1202892cde4e50d38a5a6721b9005da4f49b0e6fae7180bad9e80d3f7ffccc16dd9a076caf260109bad41a041f9b6230899ea4f0ce045ff0eb5941ba99cb66860f0ef736d351c197ae27eb11afbbb4f364e89e05af36520f497a3a1ba70070937b22a27d2e77b0000d296ab002a04d5c057580c51559fd094ed0197e5027e9a059fb16f298101da0872f0088a68b31e73f04a1562b51ed4245968e8142a2e1b71f23e4a9bea2656ba23a4c5bf0b7120b36e2aef626e360b5a7841cf3a46853d510a4c36f1f372c7257a8729b9235a8b66c29c091eef315dd87237b8b412d1cfc98243ca568d45b731e7c2ed0b0175ecbc0694ff931eeae2aab364103b7bceed0f0d5944983fef3b4ea79a69161c7d55e59c038eabcb22347f083907e0548cef2b1fc44883df6c39f45957895946b99586094033713987bf66114e685de5581a320bfbf766c4461ce36412ed105cbede013c40545d30ff04d23df66525028218c5f6dd59b0122864a4af0c27286163995676a7db3e05c84776eed05dc0cdd9e86181910fe9e885dc2f9eacab74aef6a17515c01d5aa7ab59d53b4af12b933a26207bd8aeab570c19ef602b13a630c1c26706abcbd68977337a93f7ac5526d4f74f971a2512472dbca6365d2ce3eebfb056aba0d1947688fa12595641656b4c4ccb8f3e837652ba62987d857a0d174a0c406174e11f9017e2e910d3e84b82fa775047f924d0c7609421e0a6949b10e3538c880fbdf8841d94c40680256aa9ead5a53988f0a4faca276c25a50a901818d25a0c50ec4a780b8330a8e61849a93315890a1896ebd2f7d2a0dc7fce0a7cda6fa0ae84f83c7bee7cef28b316ff881d0a980970174b99b521780253462ac2cd87c863c26362b22b07f4e0280db8d5c468a33e2758af2c2d810b3f04ed0b9c7d9da1f0dd6b20c60dd5421026243ac66a483c7c057790c9ad1f25d358de0d56d2e5334cb384928371327f90cc43b374065c38d5c8f9f57dd97d1d91ba31ce9fe356be07c18367cef4de49b68f317fe180ceeb7ea84624aef5c2b50d62c8d78334e652a40055e283ea47405199ae2577eb9ff69641ae51bd7c3826ccd7c27ecef2c1ce98954dc5e88dc5fa5c0bb425e629ca3a6381b26d850067d66cc80f531d9034ef4181e5ecaad8c86da9e37422fa9a6b544644ae4c71b3c2028deb771bcfbcb36cebea1d501e2ec48f62d31136ce2bc1b7718690eb0c647dc2cb857d68bbc33cc06042ff7cf97f5a3a8bd7165079813eefc2a4bbb466173e373139bae1e7db5fea7cbab68db333f533bf2c6071f05f9b0e5e9fb6bc1bc1690f1df9ba627f35c35baaddb16f1f81b3b4f8fb5a177b4acfa1308f28d4fc7e2d7170d3ad77a129994f939430832520ee644c2139803002def3c9c488f15aa27aee329dd54633ddeab14848943518eff26c0db99724a4afd4dc3530f043e766c989cefb0e14604e5708bd1a0972a6347315c88e6aa649d5a1dead00fe3b52aadcee3e4444477286b37220e4227bb691adcc8da208d525351a3b8d9971550a5898533016fcce9557ecb4ffa1e3daa8963a8eb42f4a3ab9c8b40128289fd87df1f16470c053d436fd6b2565b96751b014b0eebc571ea5da873aeb828c03b36f38c2d0f7ab90c6025a00672ddc1316f305e829037378a1cc350c83d19aaecfa14154059b8fe8b42e284c23f84a317a7d224e561c4c37a5e74107faab6595aa858ec75662dcf2878e96cdc320cbc4ca9c44175d6bf2ff7c415258c7a215622012bcd5a11eefe0c403c534cc0da14c4393a9366410220d51644246e615873380dc0d49d61d999762f34f3769fe0a4ddcb1427b8550fd7e2812ec96172b4b9b64d949fc89acdda04cd748de6c09093e40d0c25db9724a8747feeb0af75125dfe46a118b2d5a48cc6be3c805c884eec8ab5f8c9a500440b9642f6fa84bb51cf8684d08298c5c734481fae2e0ac186d208ba7c5f098172792f7e4f42bcba7552702efafe7be305624b03a26eb42c60e8459681b221a48c613cdaa14a837d3916ec88307be8d420d3b191a240a64148dee506e5da482c6b3d3019367ec7d09ccc2a18e539120fa6db5c0af734acc267725ee45e6961af858cc1f099f5ac212a37525d7c28fd33a9a627eb8d321451246dd3c926ce5d7151fdbefdde5a0e5340036999374f2aced26d2101f31322731c1b7358c443627fc473fa8842c40e426a2674a994e74e59797ec7a1dc5a8d868459caa81bf832f5fd91cce4b6976531e7d589823c69a785ab6aab7706ae758bebc8194ca159df7ddf4e146a4a9f0c9f2b0527818b0d07983dd6c896a2f63241c642d472347f2b3dd04b2dad0a284eaf27cd37adb9f9b2f686cbcfb46a51558538702a6737d1ba44f0ee44403838ddb1d1f12fd03e85f04acf1373f90e0d9e2821fd575c1cf1a245912e2b965a07a2743d47b0adbe5a3fe1c05b2aed3398b51813e621110e00dd545640bb222cceb5c83267b1c041b4e94c274c848474a44480ec993d48c08a4fe80f50efc461fb51f1c0f499e34628fa9e2c287e10e6fb05bc54c34122d7cea071a8fe63493c1b34182783e2e2e28b3c2fb65fa3b47071d29e5e093f8f16c09d4543aad09629b052e0ec3534e95992f44b4c08458d197391eb364159a3638ec3352c160c5280305b6fc7bdc4785cf2c8b1749b15068ad430099749b067d52d416917c50b8e090344f2e132901dffc8cec2b0a443955caf93535a25bda2daf4b1b951454f4a7bcbd507a025e3be535f490bb0379e1d1ac26c6b1ed3e273736b56d16eb6aae597747fb56a9dabb3afbc21a23c696bd05c101875dd0fbb876295eb3bbc2b7600e03ad293c76c32d285637eea80804e6b224d663f2f7d651c902d60c8f79320fd0426583986f3517203e4375705d26019e771818cc72bf3453c771bf021b93ebe8e891919b47fb08c3467672dc6663b243fe8469d9d931b7bdd37f71e014884b973b7a72ca1b31ed059f7018fd3112b0a9cdda7d5f3beffdf048891c62a1ef2de81c7ae016f197462033928035919e8b9f3bd957b08548eb22d8b3361954c2f9fa4a6a21ebfe1c6ae9d42b8bd2cab71d061170720525a555d9ae3ea9ecae2b20aa56c5b6b88b5c3d3f50d7b376af36f4e2e7deaabbaacdd55c0c358475f1257d0a605e05f970571ddbb9e6b8b042e5345c26101222711ff4d02b389e9d67ca406f01690ade5e6a8643934ec97c4b9aef00bc1bf1dce831d46a191f665893a2f603258340e884b644eaaa4befebf53bad4b1f7c260d0a51d1a2f9befbf22f2d2fdb990059ec9245709575f26b9fbef3866ca44945b828dd4d1dae91680a5e580619a8a9770dd32805c52c56e3387847f60b8864b64b2ac2030c7eb24d5f840ab64e7773c17274f7d2ecaca91ec3a0536f0a1c2a3dfd97de028b87b301fa440c25951c0a9dd740f74df014b917b08a2c6875b9d6311ec440c1d24882bebc2780f2fd75a8a7cb51811564fc1b02749986ba614e2b3a38686bd925ae0eb47f27a0a4467dc719947a339fcf015f367dc1de52242340f2c295c308a6e1d9ca480d25826f494103474ac112462adb60b94bb81789737668384df8cbf56ff93fb49b0b64c922553dbbf643fee7a3edb8b49e0bd7c9300b9c232135c96c175f8cd985d7d0520b56fb2aa1dac8730ce4075cb94ac36b5dc8b5c63050d18b93dd158dfa0e3832f5854bccd8b9fb00cae082d8f12527848906a067314dfdca45b728e30a7c86852f6e6cc55feec782aea58f432e8ce0949c37e9d90e6fbdddfcce8a7d8099d8488bbcb58e9f804201015bb45f69677d32aa38626bb5d3363eadbe769c24430c2da32b2b6743523b1be20404ac670dd6433b8709d7c29a89cd2369de6528e6c54b82efa90f8ebe8b49a144b854e25f8087363711189b39c67ea1e094ad1ae531e5d76f7c901afdb71d64026ff9b0e70ff270bde66c9569dc1bd228326dcd79dcd5814c2c621f476d59f01e2b7cd359c15a007b1bdb675b8c790c0d31f9d9b93945bbd1d24465a0a9b05d49a68bc3536125d181b212c2e28a861a1161169eef41833e5945b075721087347b756b6c3e4f711d3680100335bf45144d01700fcfe1f6b77f7b98396dea6a355268acbc9ceedd6b3906f88bddc296446d14e3f94f9c47fd6d7828fe387b7a3ced6445ae36e858866e5a345177d2567b50c8089d56ea476b9b78768f4fc68a2c9df42f7c3581ce10a80b63ec6d82ba2b0cbc34b1f594e852a6ae83983328a06245e295b0b8d34250fe54216441201add4a77567ea97ca32c834c2dcd91d11c443f9968604940fc61469806bdd88fb5ab1528d9feac47074334c90fe54b739bb9771053744633d12daa97b27769bf2dbc3617110196d80b1124cffcb4b164264a6783b22446ad31ef8098d92981c54dd9e4254a32210d31770ff49354065243e8431e1a77f595bb29ca8bcf829dff00b39c1250698f2b01de9d9af31ed7f44404b1f21982e06cb30618676e41dda64a90ebb8fca6e43d4b293563cac312cfeffa0b49122b8fba0aaee3247c2b0c7d3db96368a3b2daff04f6888108f3b23b8220f1c824885c94df2ce49a8e93ac0504368164cdbe47a0962326bfa0bea69892f1dbebf74364e7b2a66671d7f31942ce83ca557998f79baa1be5289b65956d6ec47e77f1b48800d24baf89ca4a8a27d0d9d63596f06c20be5b0fe99b1696ed7ff1642dc71717f4acff6dacf13f38031f2c0ac794d00c5708838322ff3667333948513f595dce4f3a6ecb6916d36010c9089cf477c29468d6cfd4ab91a1ac9bf25db50e4962a1991053ae2fc55ca95b04829be0ea125d966449b72a5d5b2efded44e7aca74067c0986dc2f30eb0dfdfc6a8e440411bb7ab8a9c1ebf2ce7ca68da42a1f81e2554e24b9566620bb14c4c9b94b05cdb24945d70a54b959642064dbd17761831b086bf23fd815be16b196383570e71a7f29f5c1f45bc65af43c0030cfb3ef86ce2c693de236aa91eb2c331ceb1fcc5d164e807994e0b7612a4912e3f3c5b93d58610c51674698c7da2c4b677c3d8ea6cd53dae8c3fb6ea731a9f76064e817aadfb724b9dc7940ed6293dde5cb5093c740ac0a8cda1f605944b88f58b3b4a0a0c22c0695221d33b3e3fdaa532129c0ed29fd32b010d23dac4648d524375dfc3e5759bc99415c2a07fa31658dc1e6b3361da808bdc9083d1710582122f820cba2c43d88244b16576f46029e05b808fa53a26f6e49696e5baf9bac71512d6a69e041568890140cfc4e227cfd303c984ffcbb15bd5f447733b82da7a0fe1b41546536bb7d70c6c5ac7b8503f7d9ed05376e66bf2f1cb89cd53eb8e262d67de184fbacf6801337b3c79eabe95a838fa36436455f5198f53ef8c1a8e235106e4d91d2b89f6d9f7025b3576a0a9bdd074eb8cd760f38f1563f52c996dc03475ccd2c2c34857d813d53a064e2d47275a6389c7c69d654c69b29da8921b2c9396bc48de6e05e739d69c3f8e7083ffb2653e4d2c064a1ee26720573392007eb82f4895415e394992214ab5950ed2a41863f9e79f1ece3f3229d7cc733e57def9935c5d70920a74ee01211997d225292ee9e7d9667fa9e982742d0753f3969c34f140cb75695cd17d52acc1e6436fe5e1d1b14fe252260fe8c5981c167c950a0c7f352650b3efd1022da6c877bfe411f2f86d637c4cb0c986230c2da100185a140d646c4e11c4081b0771ce9916b1224f6c7669354125d3ec59122892dff99e964ad83d65d2520701d0b9c64ebf5d409fab4c37e4727f275b1e8e83e2885917fb7138df07448bf8177cd74fa6e131449ae8dc61c714db500cf8e555d2b7674860e99e3ddfe454709e77a9d0074d3c0b28d5c290c05a81d30f39f74c04b944d15e31854e995c8cf56e35e53425c8644edb23caa3d176ab6cffbbf5f6e2c8088e6c31ba05a30c58c0f41eedf3ce310c5a10870f4a92193805bc7e10a1acbbfbe9087bc7f96713624d4096ab898bba72ac31bc80c0195d9a2f4e2671b328e53decd3ace9b9b0a381d0e556a1d67dce61149f8c96ae65d9ee6e8f8ea27e15a7f0dbfa97299e6b497e1575639a8254a627533ed5ae85a72b9084fdbb6092db598c38dfa94c7db71f473553ea1df17fff8af2d40e70724446fa8d48e45b6d9adfa18f76c5f98f4d2b5577c6ff0847b3d59dd5b16f80c5d019cdcd296fcbfdcbce8d9aef2c22bd65e9083abadaf1dd4ec9347d987f3b4aaa3712279dd58e3924c4dd9a9b08d52cdfce88f0b425537e972d148411171adfff85ad71a105abef0386bc319e688907fc5f6b389669c89613090e2e80bd7e610390ee617af6a6074966d812642fe9eebfb147cd8fc58b3151e3a27b49e87cb93f71938ff984e4838f74a23d0041bdd8d61caac199eb3d28b61ae7e6d19be2081bb07b4799c1c00bf26c7a7f2f27196b865d747fa0becdbf035da94013a43c261aeea0e8d233ff77c302241019ef9bdf199c960d368a2dafb0bee4f5c7516f6e1c7fd1161c509122761c97f965e10940345694b574680d94368a1c4837e907a2e658969b3bf59e27f28aac4ac9a47692ef6097951a70d35863a55dfa9aed1fbe981f0711338d9e1dff59c672070e6380a1d01106b369b17a870e502f143af0ee7222fd021818fb61bd4dc83d0faebe59b981a23e57d59464882b62c2a41593f48c2fb33d2b356852175d8dc01f17097bac34bf41662a1152c553a80fa6fc4c303a27e8d25c612387f29ade21f76f499638ad7f5bb31f1576a9c9ed0380fb281968032e87aaa99571b6bad614dbf1d7152bf362b2b4cf7287d7ddb89fc83d5c2c71a7deb36fa3908a768664c8498e8348e0a566425f5fbe1d04027795ad5bdc9ea32f7d9d651844cb94e09c31897f34ca88825544a0a8a822cf4a12d4c812991eb99faeda06112633c53df9968cd74931823b0761c2bcb0689d35e870ee3e092065dda3a7f773b9a6751aab42adc14137dde2e5c9cb0c8b5c7024782869bc348b0f9548cdceee1ca8e8e7f3912703fa0f387f5c4aae985f9fba23471b094d4592010722b2701af8f67a07634e6481d59a818df7805b12262dfe1e3275005bf9308593974c3eb88b579759bfc7ae5e9c3d964f30d6f92174258e66b1f861095544131c92b3d2f62976001bd721f71091002c0ba853e9f2cf4eb11c3316389d37c3dbc5589516e5f6f16743d205539674241c9169d725df1a326a111aeb350b68c6bafa46f1766d621ab9f2069ff40419456682a5f31d9250418c6a1c434e291ac3279c03ea8c45768e7663bbf6053741f07df2d029e09d91469d98ab9e0fe0073607b6ff8145d1457f2ce99b4ddffa3b26e9962bf070deb16728ceb8e7a6e698fbb6200fbe9576e2116afbcdcfa3ae023ceccad1f764f37049503a188ff175d611629c212f4c311203c0e2438336e660e65d05c6d1a106c2cf3cc17d56441b74fbae0bc8c699578544805813649b103c100a95f8ffe07b568e100827d1c182c2754ea10342c5a0ad6b0502b0f4d0d70fdf484bdf3979d458a03853b440a7fa0f81c444f9287c69c3bc3a30f46672643c5252f48678302b02eef5a0b15ad183e1e6330f37a2c27a7677da456389be488c1d6d4cc94ebd18e1eb36a988d121db0505009a3eda427db21f98de1925ca977101c7c8f9d493accc7968901958ae0faaeec982f686c694caeeaee2ec8adbb373a6a1df043fadc600e25c62c1722b3421f9436e60d6f250b6e1d3ff450e861c4452609ee440c4bf02125be23e39d5e7402fa3bf0400f932e8dd96b3f77f54e4c78f8240f01fff25da04acdab81d49fe0a14a004e138583a15e9f03774395343d3d9715fd99ff60a89791aa238a4d58bd719cf59e8ad325cea1a48dba869653585cd4dcb55d44ac11954e983d0c30d1e7650df4c3c5928f0629cecd18917b5951ee9a7a432ba10a320afb4eecb577451d2deae4cbd83b3dc74da8b10a4da34e3215550c68eb6edea965493b48404a71b512c90d7c29e55ac9103f8d662ac4a8a25acbd416f6c449d7c5961f0aeded282977029a06b781fb35c1ce3e7399041d81b312053a2988ace98357a2ed4429e0eb59aa8efdc037401e07ad0f30cb4e6df7e6b59c43c70cb365286f8fe7c04724a331b78eeda118aa213c23987a01521462024c3ea0065241d69d144ac1386e6e094d141755a9a37e6f2e6bde1cb84a844a80662b7c6225b34bcdead32cd7b4dae63c6633e6fc1dac881449f080044eca275403b155bb2b5a939dbe814180001245bef27834d5cb39eb1ce9ea9f1ec5defb4b4c157846d62a4712ac40ab7e1421f8ededdc3c96e389744ca9e419cce6f652c8d38f2a8c0e39726ee0b0f8d0e2d7793de8a80f79769ec46a9b57acbd83b6289923a67efb34b3b6eafc542a6189a003fe83093e805f40915009388e1879b44a3cd4e0e11e918223cba31f102879dd427b59a5fe1ee1db306171d077ceb7e07be9c9a993a0aec9de07ea6cdb22ad24bcca1f8ecdc1ebc5599fc9d6b5920bdf2301453c81edebc541faa7a1bc254ed771196274e6cde5565fad333ef3467098cc02eb11087fa0ffb54b1d3088ca4984a945c09c266b73349dba39e7030d3f7e08d5966aac10f9dd900ce81a20e7793a65f721711ec84ce331f5194d6521cdf52ca9bde54a6d05dfdeba87cd8f2ca9a6cc2a344c9c19c926785a233a123d539362831d9c0737007c8bb7ac328693717125cb024b764c97a6457fb0fbb5e4544cf1eac0c3e57c32800f43d8e460eab407c8bacf1f40fc09d9023c32dc1cdc889ea0a86900bd118eb859b7de912a2ea813bd1ef851b914c1b3a977b1ea3a7936577a6ac076fad336937322e5e977b97b612ec4d6bd2fce0ecb1dd81a0abe0ea2c101f13f4d49801102342f591333e166bd4df8a05441aed5b4d5e7122c53116d5c88d6ad4426afb93a35bbe7df4b188832a480a81e21fec59b263a3d81ca1eeb922d65a1d7ca89ee7d3755a4e0d29bb2e3d970ee8ee80ef3902cb0a5678417e4ca82e2821847e9174021589697f70ca38a0151cd015a526e4b2807b0e31b70c6423255fc4bb3a923e8ecd158b2d53c7ef36c631a06b6b40875c02e9c1c4929e1ab16e268aa360dafc6fa29f8e2f03b7afd94a23d810bc3ec23a540a94243aa115eb1f3fb67431da9025184bbd9bb1aa1afbff9552679171f5a0ffcd012f8454888c3c812b06898c358519ea0a14616baf9764852659999b0e7e83bb9ac94ecd7306f332c0690f40b1d73f5f6a00acda766903fd115e311134d3dd28b0cf303c16fd0467aa1b1a0db8e3b70277f8954507362874685b8564d80e01ed28880ee3636e61f709169b0812915517ff052dff463c18c26d0ebcca5f314b722b8c0490cefdd46ac93e62637781bef8e5cf8839d69e222d3b0661000046ac12bc8d0302d24cfb8b55cb45e8e9807a89ced9c5b95b1d39bd454af076dd2e57c6acf24842ece4ef90aa0564792b31a7f45f1d411bbed3757479aa4362025f0f4137f1b108b84ce17890127bb60f22e1b0ba4f6edf422cbf6a46877aea3fd4c82c929bc015feb78a2f171e815e8c0d9dc2f72006d0956ce3ace9270d29da23212e87c576baf8905c88b8c86789408b3117d25a7219bd96ac588a5ebb9d30835a272b514a1a888d2858259f38cef745c6495d3248b21502f0c42f4b52dafde246d3e01ae19074366b4d6e6ceaf1376933bdadca019c1baa9f14a63524581dc78b1f6d92578b5ff58c533b2b62a8acb52446b5cb3f48a229d2e7a234891a55ca05f045c9b12ca2a1923152b60a895941474e9a9778da3a308c7825d9cd486cf1e83af48067e2c7d18a03aa1be717008c93fcdfac962e7725905a360a50053213990d8caa11202767bb8962b162204242a22e254cb2d8b9aaa0bad36b3825edf0ba79e6a464e76a1d33dc8893e97cb5d7f9c03a8f4931055ab051ba11fc179a2af418871b52a822b6f82a67fc50fbad1aaca73f6bfdc0db79fb759e892f7257cf16255c660a087e57f0c71c2d8f02dd26f5c6d0d5f82b4ad82123a02cd179c5d6b98c0e459403c05b1e68bf88e3415d09fdb2f9dd5d5af8bbc542b0ad90cc625b4705ac91315ae07f7bd6e110fd4259cdb615a1a90656107468169698530a384669754c78ce5bf57f9fd7a870668c23d6190e6acc09cd5d23093f1ccf8d397ea67fcaeb607dab2f384bf13299266aebc4f8d6f15b445b393d804032174260a57bb5b06d9847fc299fe56b716e4765028c2314345763c476400be367031edcb3aa1b9445fb4cadb2ff2acb3e13c680fd11323af102b4bc60516c476b6915c9d5b092b8e3d4d6a98a83aca0561f83f6e0c60d88f4ce473b66968e485924bd6a1ac8546bf75306b20a1817ef10cd0423437db411f1c5ad5050c68a4b66ec358ef02a240d9962ab3423a2352cb7bb3c52255ef1a4e155d37ec4e597884c285ae4667f73b1914f1e8f3f9eddc67b9c98ae9f31afb0ae5007ece6533c00ad536b05e80c3142d64b376814aebb79d85242902328cf0d58e48b85e7f930937834cc64e1454d14861fec4d30876cfbb089805a72f4fc1c8db29ff6b47e2b3c415cd8e86423d3cab991c6b8d00298d3afa27a5f73bc2245412a479b8cc5c1ff10d8e6861da7db4c3c70cb62f16781c089a16febc2e71dc530e1b27358dd23c6a959ed8dbb7562825b96d07a49c21c9e0718ded5584dc5b369ad7e5119b31384a065cb610540c88f1d5d55bd0e465c35c121b66cb29f8fbf0d9276abaa1e0b6489cb0fd49668c990f7ebb15717baba6367cd944573987e78fd2eabc2f45003008e0447489b046c1f68dfa04c53713f8560153bb89bdfe48f900faa6a8648e8f80b390f2b29a5fe3568cf4df7d748e42ff52856c1651d115377f2dfe47011eac51da9c86b99cdd29c00cd432c14095a3fdd1362776b4d276602bb3829dcb4f645b72a4a9cd194c68da205342db3c09f01d8d3f73f0fded3e84bf9862ec1091a0a672f811a22affb0fde647a74b841057fcdca65eb79349c883f1972c902f236e21771492d2d8a0c26b0cb4af1e3688d207f328c20108176432243c0a5ae3ca4ac3e4f59f79cf242e4262c9e0c17a727777eaa230c2139f4e31f819c84adc7954d449d858d9dd1dab119b4eb499e5c483165267d70b6a946154e1d311a04aa2ec75f2d410a29fdb65d99d46e1808b9f5a04d0cd33a773a105fa29b3c0d2290c945d5e37f5e49aa1781e57bdb3d99f2196172c3c8d6a7af17b057dec786ce8635d82d418a9b1b7f4775207f9024299d7f7fec6ae98684635b7551abf24b18724b8aac065f16cb2ec58556deeda5984d09152628f76c6a26422e168582ea7b402d5e31408af4e59de2666839a32c3d40e514823e244c084ee13bdbeb4c5ed8fd96365bfe805da29e0633cb1ab464d556d8b50bf7be02af06af60df72f408240dcde94f82329e502ef27d263613767344fad7a75d8916b28ad46d1cb49bb49ff1c4619e631111ace6851a00b9e867be13ca61ac0e068785a89f74e547d8fcfa45773294ce712bba64d8094303411010cc94290e4ae993f5998c919aa21130efaf373e8231db827021e40ce33df80cc21d999e82526efe3fe257dfaafb4564ad07abd3dd338d055728ee24d6a7cece41241983bba9c51518965f2704376fc466316aaa5d291b3cefb91dbbbb996d670802995604607438ff4820ca2a246a0a483939f9364ebea9bc80ec6a5e662dc909086a8512787e5048b296e7cdb63b037b5690b7d74db44dd383edc370704aa02ba1eb2685e505815271d4985942aabd2fd650cd421c9df55d618cbffc1b5e1d35c1266c4f6e8cf611b7c4658be5876f5fe1e8487007ec401d73f7891f387bb56956530596ab0b3ecd4e940b490cddc519345d45beb2af8ab258410228dec4df6de72ef56064806ac069367fa34aa6134fb5064bb15694a44a0cdd21968969802354bb3340b0894851765e3b17dda1704746fcb911de4c3b337b2cf87f6c916c92259fa44ea03a174a627db5bda8ad71b1d8cd19aa815d097c9d0e4ec9142b2a5421ab4317e54c6e4994ecc27cc9ee9435da8103aa4411c13357f5ab265c9face39afcd89633f63ce3c7fa32a9ee2a9669f261b8f79e7f5fb6bce6ccb3eb7ecbb27cd8684f02e9a9892d93e5e1fe4f9855a7d8a9766fbe8b72dc79b3cd1645b666827d0af1664fb30468cf135a22ed6d501fb821f31428147440e2f11c838e5c41a604044c9d8434cddafb058de6c98fa0b4273ff47f7fe3c4db4f25d974b0b4b4897ec8fcc3092edc3159048b6a794524aa9a4b4fe4a4a4b37defdcdaf6c5f8d342a76a5e9d30f99b5cf645b65b2ad49b2ad47b265d59a6cbffae2e58cea4eb671c6d69f2dbb60b6675f1099b52fbccf9ec1d040f84a7221b98e5c17be5ca2c91ec31708a2c97ec35708a48a107c0d0972f3fc621ac7445d44ba64dfba349e0d8e4eca5544b6c79b8dce1988299beda94f9f66626a8e7a5aac2555bef08794ed3fba8cbbe106190dca1b1a94b141ca93eda90ecda8b562df342665d4c23bc94d4f90edc32df76b44d113c49205bd5317a681dcb17331633a48106e1ccac8b7971925b1207bda6a75b4a5615dbf6c3ab4635f109939d607fac2eaa3fb1a46c3f183cb111bf95344a6755a606aa60f257271bff0d87ebf203267bf361bd9b52b67b846ffe28b75b5d87f34f25e3f2f986cdf2f9b8efab51109e36a89267b8a2f96064097bff7915dabd1306de405f442baf62a38b97dc3740052dc9ec3588a0f6cfb38298bbad0b4a0b1b0d9e8cfc7f6f54df6b50da826a4b949f7da0784ce1ed29d3cfd32b52bf250466ded4a973ac66b8d78ecb3939681f068a22e96b5d65a0bb2b760c6ae0ef9f90591f97e5e83f63d6f2f1eae8194a00a3b78e209254f709fdc27f779797924cff77fe0653c985c917db871f36d970669c442b97a3f5ed8f31e742a756689de66df3ecaf16ee6ea62df6af5c30fb45b3a269cab986ec5549db595c8f4dbb6639eab55abb3d21b511dd3f2bc917cfb907f8e39942d2fb2fc5a4816c36964ce603af0c2d9833d9c39d32726d7f6992e3a584e9fb41dce8998eabc77dd351c7add376d304cb369905e9e4677e3dccc154b976c56397889a79f2c8d0a9d3e8b7886f034d2a58ee9dcdb8d6973d327ecc370fad4539b2f3124595863efd03ba786be988e99ab9fe93373664facb1b5fe696627e7a64693c951ca541a693025d3c903cfc7a43ee49be58198d2ce9dfbb4cfdb78f4651ffbec6950f6fce68f8f17b64fa6f4f4b3a74f24f99092a4ec997a4a442cd1a3e01e91e95f5e90e99b06995ea67732a5f46addd22797889d666c3e49a6f41a9630546ee02f568c421ecbb64ec73448bfe196d199a8a6b3e2ded1f1a691eca0d34f23624a3b0d270cfed16529f395f21a8685f73704438924d357d64e9f405f8c8e99a8f9d2257a3a5fbcd17cf9ed984c7b76385fee7cb10dd27bf35dede84f1d74f041ca315f3f5faf83d6226bcbd54d908964d3a8010954e02931f2f5ec1acd7e0e38a27a441b72284329d35a317dc437ea69bdaca7312b8d09815fef71454c7108fa9a524a29a535405146a6322e2ae715af0ca28f4aa86073dd8e68057ed58941bf8e02023d5f18e81908fbaeab3c7a49ad40b3040f2dc96711df88190bb37ca373b4e18019d9078e1939da833e7fc5527c668222f418bdd87de278318da6781af38db59b601438f446cfd93b1cba90b30f888c98fb50fec38c3c3fb0c70e7235238da6fb8959b4bc50b258240e2c48fc985658e58eafa18c899b0d0cfac2941c57f3f3f2bc67815506e2fe9012d3438ecf6e0c1ae5a4341e05013b4c94bc97156d48a07910f788516d72ad9737f532a6ce8052fa14cac8135b9cc8d8226a0790168fd2600a8b17bf94ace48487a4ca473c2ff18df8193f3f3fda60f76ba4124992f8cd17583cd0491da324f5d7a3c12ab3dc21bc291e8e547cb14b275fecd20d3234190dde4943ef9a325e7d7da105f2fcb42bf2c2104c41d97680b9c6bdf7de5b818bb51b8a8f4aa6709377c0a206edd79321d0b36b144228f128a594524a8fd0aab4d6d75a6bad94d2955a6bad95d6fa5a6b3dadb5d6fa5a6badb52261b68fcf0e3e3c999e877eee6cca29cce410cc4aa62093e9293d18a3374951b040490962947a4a78f3f431fa145e7a90be07ce3cc292254b962c59b264c992254b962c599203b3a7c7c3b9c3a300d76aa20ef0d12206f87a7c231e5f01be2a7aa81d7b86615a841661054f050538982910ed3f200084e6901699a8027cf2a54bf5d743f185dea8fb68117ad06f34d12fb41c068266ed936692b0eddaf6499afc436659134df517ff903c1963515282571f73488bd0227d02f5e9a54f1174d1c6c344ca8cdd5b7b149b8e7b7b77dc5b12103a894420883c87d887b61af61a0e83c876cae47ab10f33a35a79103302e58a16af6c7b9a6dbfe6681982233c410a4dbbb6b6f190dd12a75b87a729b517882a31b56ac2ea5ecf1b8d6ecb58e45ebbc4bb802c82c6284990e92f24d94a4ff6132fb9258b5ccd90a8c109d62fd6d0a841f95e11c16271606e9c9675ddb6cdacd3581ca76d1b97711cc7655cc771dd31eee3382ec3b287ddb36d7bc6bdc3346ed3b09a2fa63c1487cc1c7765e5b699751a4be3b2adebb68ccbb88cd3ae245dddebe564e56aada00ae2b2db6cc3f419eedc982a11537127a4349b8ee7cadd7becbea39cc6691df77befe5de94cbbe65df34f9c9cd6b97bb5dd669bff7765e77b1aebbc7befb8ea3db9cb2cbb44a2b474120109761da71f7d3eec6719f5a97659d9669d9b5675a0a8e1f5c8ed8c89f2232add30253337d28914e9bdcc67dd933ab6db85ee2fa0cd39f5c94755dd7a1300979d889c8044517f2b0aefb82709327a6b4e9c4ec993e54081d42e906388de3b8cf47a6619bb36e6edc5de1b6544cad701ba7715adc384dd3348d8ba9ecdb404c7130946e80e3b2adb2b24de3362ee3b699751a6beb384debb62ccbb22ddbb62dcb3a2cdbb0ec5b876199a671dc330d5f99db64cd64dd00f78ccbb4142a6edf6d2a52685cc7e16f985ec33567180899753cd7770d440e322a6d1368c7eff38b1d8e17efe97798a8fb4996da026537bb17e479d8c7723f4aaf5716fbb1449a258db2b9b0eb07439d30270c9dd365ce39e79c73ce39e79c73ce39e7acf3ded08046c9213d56a25c922e595cca8ffa345291e2272293a3085d7697521e870e3d10b043a33e4d6e3ce4c4e1e22deee2cba141392790169ff2c5b3daa7f649dfb556d904cffe46fae21ac64eb18b577c10e81cdede61ee1eee4237c1282ec2263fc1a2a748f11156f11578d402ffb0c00f79857bbd110a7e711246398b6758e60fb320e1824587e3e9b4c5de2b6b1be60f3b896c67ba649ffd7a331998b32f6c1630b7c0316b2b7e40203bb6f1d03ea7f6155a8b162f70d7d8dcb8c0b388972ed9cb06c4524c51dbd6b6c098d6e26614dc35b86d70df344ee7a8c0ad9302f7ce096e11eefe992b3c591ab4eff064cd96065d84cc21441afcf02ca24bf61a9e466ef40ce83547c6742b46b696ded21cc668a2946e3dd3204d07bc50e6ec3021d7778ed4913bb5566f925a44842ca684d78c468d50a3d68c2835100ac72c592ca6b0c783a46aeb470e3ba5d7d5574bece953df3ede203463ffd1652cbb33b22cfbcd7046f7de3b59b79bb8ddc4ed266e37715f23aa9bb8daefcd361e5c9675e7de65daed1cb0e59365f4d33dbd93ed35cdc3b4af6bb2b9f1c8beb601bd6f1ab4ade1c8ae2bcb70cc3646323093f429669b3b39f3a626db996c1feda7cbbd3df88f469e375db27d4cc1f183cb91eed6e9ee6e71d8475bda77d09781b49a06b383be20326fb75b8ed62eedf2c294c8f469b067f234681b8784f0c28b75b95c20c8f6574b6f3a409f8d2f6d459accf3b26cf698106990ce79ee1808d4df3eb377f7fbd8fccd7ebffd6ebfd9bb5ed2ed9976ec8921f9851c95506127afc08b3a2001916c068d62ee17a6cf7a1a94f26edbbca1c1e6b11f59ac5655ccd9c41b8ffe6e9813eb30ee8dbd3f179b8e79ee0b2273f785ddb7b7d878cc63d9e9b4e52865ea0dc1c33dd3340d5a1cf62f7d4434d9376e23a28a7dc5cde1de70b340aad863b891682394bed874c84fb24020d01017d68a67f2803008049a3c9307e402949dc3f5a0962ed91fb9583356ec4b372cf79bdb67109ae77f74395eeb5f38dd331a255d70ee0ee56eeedf7834d87d5d1db06f898086081b1ca993e272c446fe1491699d16989ae9438970b8c55623932cf393ac1074d912235dc07f34f2bcd92f3159bab8481797f930fbbc2669d3d1cfbeb0fb4299adc6e1fa79b1e45b749383308683dc4cbfe1205ea6cffa067bd84c64d2c603f4cea4d6357dc23aa662b667b1e900bd3f7d84e62fd644f58bc76d17e1098568b237c1d389a8628f024b8c47de17dedb7d5b1f2459546c3afad8e4e9977e89a69ed605a3e982962ee5508b275f2e24b344236746325e122447b02b5f2aabc14ad20e84b6d423b9dedb6d1a76d55bafd5a66157adb7d65a2b928aa422a9489080e09c586dc1adac58aab5031175c1802fc8b51e31428147048c08648c79a92d7d225dd7f50b5bdd3b64c807ca9714359451c8f5b2d5b54a9e0b471ec9552639c288a8ba7ea4ca4b4c955e6fbb37dbda87d993abb88a5f3813d5adf9235595c58b2cd7cf05b382b1a391e7ddeb7919a599da0eac03d6810980c3bbcaf539300e7c03dbc035700e780018079c310d2c001c000c007c0356e1177068634e70686710da1884d605372e1009030e690a727d0c1c522372bd098714895c1f43bd0a0e290c72fd0a0ee90c72bd0c38a435c8f533e090de20d7d38043aa24d7cbc0215522d7d78043ba44ae3fe190fa20d7a370488190eb67e0900a21d7db8043ca44ae4fe1903691eb411c5224e47a1770489590eb5bc02ce0903a91eb61e09046814e21d72ae40ae334c3c00fa97c69cd9b2b8b545db40a51659324b9c24f4c6553882a1b13f344aea7f5340a516565a44abdcc925c4f7b620af47aea44a550c815634254d9991927ecc4d4a584a8b234344bd089290d0951656b6a929013535d1351656d6c8e90abc74454d99b9b22e41a124254591c1c22e46a0284a8b2393941c8f5b426a644afa73e882aab2355ea757e90eb6889a8b23b4a4495e5511255b6e70651657d58bc925e515e4f6362cac5777ac0b3835c73907d7090eb690da2cafecc20aaee0a065175599288aacb922a4962eac5eb5f4f8fc4548bd75398985af11f1bac68c02283cc7a414ce1d75314201155b745aa5423318542255952a56eafa729882abb922a959e20aa2a4baad4b720d323725da920d7672df00b5f815b1c055ef16d3bc6f7192efdc3190c30c4886132c510838acaca8a0c32cc30030d34c89051430da7130a3563860d36a440175a608185e317fe02ff2ef08ba360172c2ec2a39b60d143d8e4de350d84b1634cff61d0e58b7db5b7b7bf5630c070ad92c4889124c6648a9189210699968a4a6b66656586460619686a6698a1c686061a6c6e64c8b8c1a9a1069c9cd329470785d2d999316327d75b1e1b6c78d8f5b627e503bedefeb8b06ae1f5978585a70e5a1f177e720b2b165872fd65a5c0f8f0eb2b2b8505e3df5978857158b3fd87432cdbc3c0e196ed53300b383cc9f62d601770d822db8338854316b2bd0d78060e4dd91e854319b2fd09873564fb1a706843b697814300647b1aec67c0615492ed65c0615422dbafe0302e91ed5570187d90ed63c0261c462164fb18388c4c647b1870189bc8d6de68b247b1027bd164df028ba2c9fe053c8a26fb639468b27f811f4df62e704a34d9a360309aec49384634d9b3c02bd1643fc23266dc104df6214c239aec3d9c030e1da2c9fec23d4078071ea2c9be847b88261bb11388058e5660a3d6cf4c56c951c9133639bbb4cc522ba913c0e3d18a908d95b2501b80c7a31573482cd5ec71121151bd8358aa870183443231e92cafb424cd03a1178b8ecb48ae2f31c10be79022a7d0ae724c699d910ea1f848a6759274955f782beb7ade68f4574148067354f2b393b3d831b2a675f13dce9416c0150f5db8e8e1c824a43bd843ca845c4f63264803e3e18cfae2e1e807bff0baf085b7852ff4bc940fc6178ebe6f0e9943ee2d0b64d5ff454b9bb6ab2f045ba0cc7021877635b2ab192461249aea33cc6a2191eb2b0bcccf7b4639b9bec6094b5cc9a8065e7deb25e4018f3ec9cd4f929c92a3929f2199523a736f9f68e5149a8a2dc7cc59f77bad2e16fa7263ca88986a41ec23e22993aadb52a40e691bb40c726de9178d521aca971dd9e99c94ce8d448f49d8ef69013ed2c3793373e6cee80be78fe8f7d91591eebdc93d98b18b2e8672492c568c2686596a2fe9de25cbbefeb648957469c99b76c3bb8e61d8439748c35d961ae634bc6958d370a6619086310d5f0d5b0d5f1aae1aa61a9e59c3ad6199bf180daedcf8d0dd2a422d5ecc31d7afd49a3813656ec8b546fb8cda0fb469d8cd72785dab9713ec25d64b239b0bf1279b8d6ca465b7bdd9a039997e61ca0f29d9cb17eeb224124da077f9c29ccf85b79a2d5f5883d1f285b3d992e50b837440f9c2980c962f7cbbc8cd17b63f974d3d52f385698e2610cd179ef98768029d46e39829d1043a912d4729d7642cb61ab4488345843a70f27a091353295e2f938829eef554080c44ba92aa392355eaabcf0872bdf4037366952bcaa623bba5b7945a7a91680afc4392e8edb16714936a485a9d35157ba1b95613458b74a9fe5e4ffb4ebf10a42fa2651c16da76d06bde96835e2391e85753138462efb61ad847a29f0e50d218b9f2a5412338a4f5b3a6510778953f06781831a2622c8571499657c8f2a1178e1e104bf2f6dba1c159b9bad58040839a74094db29cf30a3994794eb9edc06e2746e3ce7943e4455303fa44ca71730d65ae3cf4c0a274054f5e21b74b4a839a7c4602dd850492421a6c22a6640d264e867b34985dc30fa3aa8b902af2d7a5bc4c1253a0dfeb79a3112679e6bd9e1767172155d245fbc296dc7b640cfb82c0617bc4c0f60a0481c3768a4317a95a2263973dc89d41589099553081ed31cf1edbb71dc318f689430aacb2fd9c19080287384bb206596a7d8a1567f2b0caf18d796890e2cc20b4860185214030c410430c018221401082107020ca96f641b22295cdcd1a1d9bc6e62625e5a014e545afd278dd46c8b2e8f12b3466a986bc1ea44ad54e54dda30ce18689a6233536366753bb3866a11ada3bfaeb793d904b4495199a50e8d9c530afc60b65cb4617a9e06834e79c55b6224e4bcda7c8147c62ca0b4f2eba167aecd349c827e413f209ddc73b8d0edd0b79a150e8f1a3a2df066b0f96d8f14e60a6dc018fd045cfa49006ef0c2430b5eb42ef42ef42f69de854742af286e0a14091354cc3c0843e5b47f4b09590eb3ba771fa24ca1aa7036d3a445de84e1f6fd594d5d4a5e96663e6133c7366a2d13e853a6deef40905cff5460f0981e3855da48bc406bb562b66b644d8ebbaf6f2ccdb76c3c76db06edb8dd027fa4a4c78a2779f48345bad295b53b65088be4e0ad1709cd192ad99eb670ccc6cd5702a916342ef62626ad311fa687761ba1ba6fbc2a82342b1d238383831316573fde4e993e8f553e6cef4ec70c6c464214dc3354287b9dd33149a8665bfa093682431736a58e6cce67b63661fa22974cde6420be4d04751295e43f79e02cb58ea34ef2bbaee219665a87b085ff1a08d877df78669d164179b5c2f0289b61c5e76a2754edf64275a8783dc1cba3751b386a6cb6c31c193a5d3e04e97a4aa9b88e9e9234423e47af275a8c58b3d6362ea8aa9a9d3a86e1e9fbc62358ccb8c4971926f566ac0995ca74c34d5cf980fc4842e7aec93e80baf8f096e982348640bcb992bcb1d4453bd5422aad4d398a84ed25ee89ee763b61aac325f8909afca3c63fa44ea0e84e6b04f28ea6cf5a9611aac2928394260606a6612371ea53c43427892a66a76e3213a6dc9993ed16bd3213afdc9c917fa383910321fa17b7d371155ddabc9eb27ab751a27a642afde611a6684937f5627b866efe4f5343298987b4f7ef2939f3c0433112e31e1d5ebd8e7e7430404084fc332cb9f3edd8f74afbd92ee773265be196382634e18737c88a67a8b97a084869931132585b072c3f814e11919c59ccc96f1a472dc69e6b6af01d1542fb3948929d06703fb80903994e600832ca6cc99c4beeddb1e778b76bb747777777777777777777777777777bfc5b424162b462a529c884c5084bc8edbb40c845df86a1b0fd98d72172813039947b91e2b9441ed9c9f735e73b68c9d233abad7c52fee5fb885a6d9a3dceb55127631c22b4e599068710d1c073c5fa69196f1921cc9f5c5c319855c653c6f346a997fe16ca13c6c997f2198b31196f905cab78dc768743a3a1d8d5c9058ac9844a8052273385f22a657360abe9976ab6770d3744ddba8c07d9302374ee7b44ef3744ffbf4cf5c4d96c9c2b3054f970685608c27915944cbb878fdd927f96ac48b6ddcc34435a07b946bd75b6cdb21b79669b0355f24ab6560aa7d3893c82f7db25fac1b0ffb9e2f46a20e6bed376445f7755dd405a54731437060080e7472477f346a6de4695205fd4c7a3ff0425a841689a64a63962f51a53ec543b177c59b81ba565db2aba8425d6c29d621b9d63320aaac0ba44ab5ef610151656310835ced79504054d919ec908018848cddde00f63d62eabafd0ecdb65a35620eb27d0e31e5dd3e876e0f0001646b6f83e8f635a0b2bd0c1968c8f62b31c5224725db9ba2cae6e8c090ed4b516575ec59882abb638fa3caf2c48829d2edc19842b97d4a4cb978d1f322dbb7882adb63bf22aaac8f8a6c7f1255f627145577851253bffd28a65adc5e14532b6eefc5148adb2daa2e8b3d165597655fa3eab6cc6c5951a5de5e8ba9edf637a6620a3fe5bdf1b0d4d24f2a5f6ce4b8c7789222c5637ce185c7a8523dc61b6e788c0000c0630c40001ea30004f01869d0788c393f461c70788c0318c063cc21871a8fb1868dc768e3c663bcf11871e0c841001d3aa878d879c763ccf11809f018753c461d1e557cc74b3803e1ef180669f87a8735131c1a61d159e0d14998c55130e92e30ca5f60173f7ef116f829187f070e25ab85c572573f3e3d3c3b3a393837363534332d9998243388419855a8e40c7785450dd5cc0c00000028e31400003014080584c2d178204a439d8e0f14800c7fae54684c1b49b42cc7711042861063000000000000000000c80c6d00c7522080af9225bc55081bad444de53926bbaf896c9734692cbde52adf29c99825e52ff9a8f0c8d11ba235a77af1f2e495527249975119b6e76996c8c314c710069c6a7d41c9913a7521a2612298e9c81c184ec25b3b98e86999150846a43e1b7efd009d93c5734befcc2c61bffe6159ee9e70d49aba17ba64cb79c66441b241cdb77290418a3f840d731aab8fe6e879caa2008fe7944a1ab90dbb80fd89b5df53eb4f1d099ea5938f64e39f372016afef4d293b932297b9be825785bb83d48b66711448dcf4a1af1eafd7442679647db550f4266015e72515888d4fb8d9cdbeba9e9763af683f671ac2be641de938c62d930d59dfd98a45e910f9a3930bf0053d866b893434bca9ccb1bde903360bf3e82e82b824e53d61b2c5b58dfa7231379d67e84716b590e56e7354ca5f8a2fd037cca2c4f3b6a9f35f8adc18a8675322c738868cc04692c787bd453c9f16a87b9b026aadea6a6b23fc200669af31436c1d6c285006b561725271311a859be0ba9c073846144115a74d3b06a78d5a78ac25b072dff49e0eb999f2ff51763141a8b9bd1c8e117bc5ae9ee1aeb195a3ddad48af5970a767bdad827b49e8a19cdeb87b0bf206716f2f65fa63f9a6259f133b30795620c6c1ef729aa80e58528149064271aeb7945d4825ba7af3032095237c9c3951d8fd427cfd62e944b41a1f780ec8cc0fd1ee4752afeef5f2abfc95886e3fff082c3f4b06e485490dd9ef2127a16681e0a015e8c70e767dbb09e99694c305250cccf11ad1659fff79267806837802f8ba86dd03bf553ebcc9c28778f67c0bbd90466082688365fc8e936fa3f48b9c5de486fbf0f2011371cb8e2ee57a54765dae27adc031d9dac670e436baf4895c5d7e7bbc5245a482ddbb09c7685bbb4cd19ea2f78317d1a34f53df033e7cfbc7b68c1a9ecb74a9f189be279d30007a04254ea07234df599c8a0d569d18ca8a6d82a1f1f5aad8bbb38d082cb3fff115640f0b590ab67cf33f7204f62e470eed562470509d9e936cdbab663fe986e95553407bc433217bcb1fab837a1ee61a9288497712abf743c04a02d33fcf00925d3c00a138be9e081be50f30f68b8f3d04566f2b28ef2a46d910bfbbfa86e061b045cc962ed23273c070f7d46285e16a177f4ae3feb0890b1e0cdffff05dca16daba9f5aa936f0cb6f3537c143121b9c7520f98dcba215b34cf3a17772c0c20196bac29644385d294a934a83c94c766c56242faede4c30a270aa6e304fdf4882a94807b2c1cec983d7c5741ba63758b8e82db24063bf1b0b20e09cdde0547b87445109e28ed80cbceb1a2917686ac28ddb8615614170e4140ee90216cba457c026d6b129121d7cfbe789714931edbeea4266ac6235730c3932d3bf468492b71dda0b48563811a90b8920cc08517c760cc0fba91094729e22035a15cb308c40a42765f0faa9f03caf8f91ebc2e7794ab666e85b33b73c97b14e3a59b27e089e3b63634ac251a1c0e414bc57cb63308a8fd48210317b92d286a4a8a624ff89b6ffe5dbe11cfd5dc8d14793ff7b5abc46b76f246f4a952e079f50b7c1b6e913e9e31dd2be57da1d9c1046791d178865269ced199878f6738bad76cd99591ea76ddca7880fd669394a0ec4e9bb637c3028a3743b683dcb033cd288711004e120a6557bf77950d050f6a12613cb69d2c0d3bd064fbc754746f006a09c36aa1de203785470aa0307a2f5bdd06b2d408866b045cae72c96ee24884d6c4cc454919584ba6f38b7997e4d8e488fd9d2e8edb4f6900d39991c629a664a1061fc38c49ca3e308aa599d2328f383fc4ff316394a2f29ebc7379e4984c34fac91ef541fd57f5420b2ff9bd731535b861769e589ae277c28eb2c1bf9a7e9b27c847a29ee38992af10cdc59735a885c7a1b3d9539f22101ffde1a84bf15c20f78e00699e7057454cf0876e9edaf37a59b26456ea2daa718696d3987560bdc00073fdd4a1f32eeb9584123d1fc8bacb6b77a3c220f2ff48bf7be60eefddd8840733467322726302226863971ff351ff9f43937ec4df68c7a73455d8e3c25992d7011651899f32089716c0695796ab35adaa2c1f1e9e1719c8008775788e94772369fb8574e8d6b48e1397b746f3596e5a58b26d75a11056dbd309518ec225f6d50d9d99a403defa49accdb2bc405661c20558dec73b907221172c609807cbf97d0bdf0cc4a909dbc7012a7086b4604646851990678dc36f1d9b08c18d4ceadad9df46d698a4faf384517fe271ba76127b3737e67eba368582028a24024af4cf48c0a2925605329718cd76caa687a820e4ea7bfa614354b21c130826a091722c8a7fa7acc2622ec825be180a893eee4087a2d8d13763fb005c5970f9054794109c12390cfa6dbc3820dd41b21f96b83f927d52436709a1f4b268b0c9fa4e64d140dea0aae94a61ecee2a6fd0af11635dd99300a47a1b21bb5a11b98b2934a68a188f5232f44cc32682c36568b091fab814bb9a43cfee8fb37381ea6a57b21214ecaf3642169262714a4ddd2f0ce6ec6eb523de91cbfd29de64ca4744254ac6d35da28e4dcda0e5c00082c949bc4c6133df55043b29f9007445d373e38788730750f1a801d0b7812c70198ab5459b0e9180f424a15061b9f580ce2e278f8b9c4f04375ca781e2ed3ad2ed0671ac799b593c84934a877e41607aa5ab3be9bb8900ab6642fc19f8e25d29bec635461c5741c138894a59eeb51582c817ec5fa77b0d09ca0108e52b6b2023520ff49a0ccb38d51eeb40df99263622b226c5dfd17f514278e894610c5b4c6093e1382f66cff0faafc3eddec766e9906f9edfb1fccc174ed5536eb3ca6d9043f6746804a7bba80da9e09e2ffa567114967959ba48fa4aa2a58530d3e3ed0531ba114ec3a21f6166bf73acacf579bcbb58a5fb4cad9af87d52fbc857db4367d7a0d4b639277d74bb7e6a5794f02b944fd9dfebe74a0884295166fcf60c892f2cbeac10b3921aa3cce154a6c1a1bd3f108d055c8a6a5c399dedc0d8a3020f74e4009f45941e4a2192e5544aaf0cd25be0b5ecf424693c2057101ff3eafbae31520e4e64772c08ced432adeb47b31ec9b6e3dbe79c21f7478721a907a1a61173034b0065228b1ee7c076524c3daae122399236a947b1d17aa3237b972edf9441404ab0db41276118ff7637f0a0a476eb081563d64aac969c18a14332ff0ab6a55a50bb76cab9a01a81e204d5d1779581a3decd375f92b7b70b9f1bea38e4818e4bd948845603dfb00f553164bb533d2cb587828210bf1b01db26d6c3d6cbb8d0e7e34c7ac1eb68da5d8b50e9ac62e4de0f71a7dee0865d650aa90516792f7be4fbff091b426b765554f3b0bbd6f989ee6b8ae8f734ee2a5dbc0a0f5ab13a988b909f088472306989d4f7c1bc5949c5023ddd8301f46723197cb56c951faaf84d47e79ba2bd025091cdaf91f0dd348bb53b260c4ba743735fa2531b993f76fd808dac89ac4e04ac47745b8df45ace16700db84d3c9900aad093b03d87908fb53b7bf821f58783dd78f7336a128856fe96791e88d252b9a07b9416b61eef70e1b88d0250636ae96a699490a24c58f894e8944a42ceca47d5c39160082c0b37b4bc067070262b283a713d8f9c5ab69192ac6b50932ee589c6ab9d85b6402943067873a54c78f8f9cc2441bfecfa8e4800cd45c3271ade0d84856f5d20dc1a3f703bde9c69abc985f98fbadd3638426e1e9a08f1b8a33573a548174808dbafffe7cfdec2d3dfe2901819fe677f460f7a088fa093e9c9ac6cd001c909512c31e33d083f7a89bd0758cfe089fdf73cd51a20299da29978334706e6708f02f89908685c1565233e339e637810def394acafc86b82803b4fa1400c901479652aed8d1a8bbf9883a323a12a98eda8785298e99b2668a0d80494153ae1c8a720bae2b21332d523780196492159479328d32a2b2d5f6f8b960a1dc843a4f2bf18274c615b02efb1a0cc755e483e32a5329aa49ce30c71f0442f2d8bedb84147b22bc0998e3a7b261adf3d2b14d188e45120263d9533179fa1720438a3314082dc47de4616a1fa22d23658250341e9aa415dbde6a366dd68ae2a957d845eec237ea6e7f6817a08745ea59ff37f891cd70db8d7f6c9303fa1c76eadb0156a7b081b4da1fcfa9fb2f37ed4b863e06573cf26bd50da6bb4dc65995de90a09c70f0d724cd99720268a2f9e821bf210ee33e72d60a0ac272fba79cee3606868404090a18d3cad297d61ad17ab39c6eec3683827c1c7482dc0c1a23a90ff10d4db7f7cfb07f327bf46d9b9f5a153c8bc72321ccba28fe8c03e22d82b54aa51286db43dedd644a04f17ff7e2b50fac586feadcb38277cf2b124ab71476768897fefb4c0fe8ef11d92c8b1a27a748b99676270ebe97eb9f347def992bdbf89156c525e659afa735707595848a06d2a7e85fbe4568e0262dea669c0f83cf1d1f1b01665adcad363f118efd37171e77d721da829e33eeba0872be53284f01b65fe81d4f1bdc10f6f5cffb7712aa755175dc106035db6cdec09a7765515ff0b7a5fb5897214c399292713b41115395f0929972038f98d8db1f74c8e60456fd42a397281f91485140188f8680509e21e498056e0eed92060a3d4c87742f02f7a12850d0fe5fdde2b1054a83621d06894560be84263ae0d71152848430271e8b38da1a37e01a53f115263cf47b2cd0c31dca3d236a8fa9fca98d4c3de930465489ad5eb4078dc808c1aca52026466a3e575c926f2a7733aab98beabb7f80e005179f7b16657e66c9206512bc9152019f877c4cf58a3892831ffe2a31160987396ae2ab491a873d9c57bb860be6a114873dcb2c9de76750d9e3ed1dd97a54ff9847d2715b05ff4b7bec4c24837d8ff430f508a029fd8e1fc9e87d5f43da26fd5b049274e903550c39216510520d1697adcd4591c80f756aaf23913d3b3405a71d85efe0ffc03f05cff73dceed89dcc052b3976f9416af684fb20296ae10d929dd595d39a9b3cf958d292f2b9d1d227b79a9814e31ca8a8f294a166ee708244c590aa55e93c2a0774452ee8ac56b82832bcafaebb75a952cd9b0fca6924bca94312d993ec6a7644c781662242faece1d77d31d613b4bef9655bb72e27775e8f152302f9774ebf63c8f98831a6708e0c887f606820f3d9c494e1bf927ad30d6f23509a72a4c0b0b8b9e555d83eb888f48498f8cb19fbf8f8ba1a88f68c6903e1f0d437ac40b8afcf97f24e2238c4884cf6f20111f413d227c6e8ec037db604a8da7ad99e966ccd284be68195c62fabd95c88d28a79d253da3220b2f57dae8b494702ee2e505d2e6dcb1f36c084c480f0ef6b265728ccc25eb8e3b1600a43f3d012438dabb040a1aa396c890686dab3dbdf41ecdf817b6fa9d2f60d4ec24bd1e3d43ed8ab5035b4a2bec29faf009d348ac7c886c35788cd4ee6fe66155e28d292d72755778ee4c82f7b2bdde0d2586bad93349923eb3eb41fc3ba0837d320d8ad02ffa04aad642e784a83a0b1123b07258b5e1d0bab4564a621b3fd6023911d96587d48a11406e30b46b880b870957c5a5090220e234cb3cbf49f810212e126440d33f32bef7231bc7569571bc5be42236931130bf4abfe2fd339ced4577d989e3326e613cc6d632e4304742243650ab7ef5fc291db2df066ba5d5ed645298761c22adce8f5b9e583c81cf3e3b1f4c2c40b410e881025ee29db826693f52aee9b172b8e42fd15c348cd595477d5398d4d138b1d5b850fe24f6fcc33b323815e7ebedb7832a4359907a40559b25de92c4bb1bf2fd99ed4c8c7f5bd46e2397b099b9f0e915d470093f9e59a898f84d604265c9a353e7948c557de07d0291ad0579d23af332ee4d2b761975805b239d6dd02d0441ca17187e2dcd9fc789d6fb87fb60c38a99abcf9b02e8fe3164b74edbf7e56a1f1cbe2f67ef79c52e89231ac428738a59fce9884ec1206d70708ec5c6a7a2161b347c455e66fe741a529d757ff604c322eab342043614e572f67b17440f9dfd5a9ae8dff1a2439e8cba3a5e960467eedc4adc6aff9971492f15376b72019d5dabb65a3c06d69793668e4259869bec024cb650c1961c2393ff8a25b2467d46e9d584a8d9477a3e380f5544cd113145dab4b1df366eb29beda3df06e2f466e1faf90dd64656b2aced311409fb5bb74fd3a803028a02a9e0056d179b9a01c33c12d848631a2e0dba97d731c3673ab836dd0e31aeda607fd27fbcdc63d2d61e8af2228d15738e79a56df646ed13e7c422c2c856746b0462d39d6426f2e3c79c7b71a4c6a0b034dbb2273f26e641e5e4b4c4a06d4c03bb4f47135ea70f55024fd746ba9db4b040c8e13dc14684f2ecfce98d085134034ed021b0bbc85baf5f1d93c6c0d07a038a686142b666ec7626de38733137c96d11c7ac67207d6cef5989c476f28c380829b67d2d5540da8d77f5b67ef9ec048a17ccd11f59b09f1529b0df87656dbb92a0926a22b337f38fcbf9a7c1f8dc47833e49dc36620d76a540aa97d3f73b4f7fbb5fc6c61b278c6b52dcd7684c60af4eaa9b8812956e338a443a8a8823112e0e547cdf578c44a163f1109a380e75f98553606906f50a78353210e354de44e80f30eaaf5066fb008fd192fcb207dd9825f6d911e00464387ecab628928321c331d2ffb869780f121d13f521da9f580f92fe693420ee9e560b79bf09dd907498d40d61ef74176941f4de6a2dbd2e19d52d9bb0bca8de333bbe2989fe97a7c0d4d7172620333b5898aa28e193ee71108a4b1d41063a91ac9c4ee2e63670f52affaeb653d03499264b9bdf6750dfa16dfeb8f2e71700ba0215e35f8822ef6cfd5d438f30c0fbe575433f380678a3af1d1d74d40890b34684c70d093f28a8f86d2326e0a2edae3be2cc3ca30ac6f63c21a9ed176424c1d4e586988c2879401987560c6281ab3b99b4c7d590987d380768017bc521eacedb10dacc859630ddb4fa19c7d366993a8269b3c8daaef163104a5b37ffe24a9ab81a3eb129b1c6963156b1dfaed80cd9efd4a75d145deba74f1ed3c4c0a72e67fd131187c40f859b289439e99532ebf62b9a5305005e15332d940b2bb5fa5e4e842eca12359653dc100d371b12386577d5d80c4ccdab32029db05c2e595938580cd07858d795d702da8a4b0224e158e7ac2a2c19b2715e56ca240b298c047fa56763529a20d92be0227dc631aabc96167a477acbb3a66a0e7e5119d26731e07d33130dfbed79df9de271157d2c2bbe7278b59c6a6c7087c7ee568ba638e06b0657b43af8d230515c207a6eb580d170faea7c1e28db04323bd5eef73683811ccdae0e41c818eec7a008b9c38abc83166e66b773c236e0aaf440323d21b0cf0360e841878c3d4a011a96b6a1a7ec85502969d29537e014e158cc580deb63c845a58a71ec250a548baf8bac90a6d567b79e1264dc0283b301603025c47fa389702de0f8ac476e4f4429e8e6561e431b191d1a140b419cb976b83c283dc1ca8de47f91b068d57d5a882d25d7ae229585d27f33df8a693db1645428fc3bc6b33288bc65e1a9b6438e85b4ca18e91f43472dceca670205da2d282ce990369a9664798bfc07667fa7256fcade6381dd287e1e367ce1eb9ae26b3fd24b78d6c8661a772180fff4741a768ca563ec4b59a4afcdbd26de44b7dde844522b6c3a04b940616506749aab8521e48a944f58ae6ed13a2d3150383ee8fbd1c6ff6a246c743a16917d6a19bc2a91a0081152f73a5d1ba8b00fd69f802f005a46a9c94fb5eb8b98d24e0f5d40bee91f84780c1785e5a41bfe0ed4fb9cdfdd783a66413b00426a72302904a78fc8bac5a03c323f3a5bc00b3d238ada068fcce35330b5ab082b8ba695a3d90fde72c62b3d7d7ad032485eb9078a86a8c0be521dd8081bf4f0b75eb501d9d7e37e6970cc5e11b8c1e5d74f4d86f74e66d9dd0c31024cd9a7ea7c6dfcdc5139b660c8564aefa8208d8076a3414a36172b0b164f9b1c994464100fe0ceda92f9b78087278127209f7e550ba649b16bb0d2e2172646786fa5dfb5fe73222f8c2a2eec4e39baaeeff2853c2b526ae32c6a7abe5f99c171e4989a67634ae18b34f8318d36a95325d42504d3bb126c5861c5c3e088d8a3c0e8987122c3f8c0faa9aeff5035740685bf752f105db520719119afc1857ab1ac4a0e6c89634457eeebd3ecc2fcf66558b039de769ace9b74e1484881e9510517d1454410374f9762109845cf2721eee2e9820f7776ed740b95685523da2b084d6d4453321bb2630ca4d4860c3df03d884c81f85e5c7aabcd4b575f9f2f6b50f9702427ac13f17cf4b4293f523cb2f08584331097ec0bee76f39cdda27b226bc9c34ae77484d7e8ca4bde91f38950e49e6070766b797e2ee49f89028c0c5be4699a35018d588d55b5cb35cdc343386123c9063f9c112018a8e8463469860159fd2b6d0fe1122f0ed41fe5612706dd5b39b84ac7c983deb90582ed8afc7a5a18f6dcc4ecf7747e5680c72c00c6bb9164acc7082132a6bd1918307de830662f8496398b96ceb845015279420b8823b96e034253bbbacb643eb9f805e6b9767ac334e65b6d62fe235573054ea789cdd7ac17c0929c3e3211f7b8960cd2586b0244bf4284227132b2d22a3614c19b1c88f8dbeb86c8fb1142cc9d5d41f4a63b20e0aeb71f6667990f75d3ab07b8eb8e87ddd96d87aa5634a743305570e972a48d0687faa7d4baa1bbc53009cb2604e3651c79d6b3f1239dc405bf59efd17ad12b8c6ad15223f76f07a5006f75f8f8290cf116dba10e75696b9211ae35a729c4d04dd225cf0351dcb0017d9ceb05c57506c09e02b10491dd08e8d41e9f667c1f9cd1caeecbbd8a16a6ac6dba5e0aec98a6e12241826c329afde2e6f66a80f4881229778650ba79ad395fd753ccd51e12bffec18da714c1b56b99c607c4964f10048db39a956e43615205a3e3306bdb9915fcad154caba25b663763bfa7236d21e6e48656da3f83e20ee20eefd006e881fb4e2bb19388507c1d78e3c55228605ac53d95cf1d67e060cdc30ed2da5f073f1d2fda2b58fac4202d3b7a4bd12e0ed2a65ffe8c3f422a7aba913996af08dcc0299ca8a924e08640e3b4ddb3c1be81ae68938cc84bc2614767f48a96fc1b2ea4487cab0a8f0f8ca7d885abb15c61bb9be8b7d043b33774ddfc69e934ba5fc44eeb884e1704c2566f1289f217f6e123df7d82089c09400e70033fa43ac4042300b97dd3eb9080223671ed5da39b87509e38498108be03a3721cf952b2b96e2f0bf0d7be01e00e36ec4a2524146cabeeef53f7a6f40c90af14c5c870c92fffaa394705bc9654a59b86afcc8a7165459cf22ee0b90a0432ada220f21f3af89eed2f86e8503b626a17f15ac0e72bdd3d6cab40fe72520f8da3414ecb3d88a80f3491b22841f496d8b85d9e54c1d10609cf36d658a3c54611f3ccca7fd4e2475c68fc5891bea7b1aae814ceedd42124088b9bee0da7d3123618cdd8abcf407a7d876061fc722d8f57ec8b6542d69fba8612d0d88ac567dd40d390ed507e66fac0834ca9c64cbe4c1c678c2788362b61f120ab70b2965946a0e25b37b50b9b130d138392d327f7e14c953e19993ec1069890aaaa2d366cdc42bf89a8a0f2282ae3e86722409da81f64da2d122e3896a4d0a241c81040362746bf445f45c3803516e99740f6ff0d2bff66045286552ce2fe751c51d3b1242130ac1cd208d333b0b71ce7d7c6ff79708e4d723a7dfde5010531315ec3b44dd82a63ac9f77de9fb133fdff112c91f8f8dd024e6b4c2593a331352e0cc93b40d9f489824c2c9b495efcc12e974d029cdb0f14e8ce5816d9211bfb5f2b7cb3259579c7b3df512bd0535f1d4b85f03db8f4186361bc124c2e3bc18828c98266eb4e021953f004c7d8c8f3e72ad8fa7510c849a61900d9bfb4578bb171497ba08d7cce85848ef5fdb295287463bada0526a659c0434343fad5548e30167c81c98a708ef0a71af85787469eb3178b78c4ba827a1341d18c95c2b1673be5b2bb79d8ec5262d033ddea16ac05a75c8d71c89957a82754b6193128170265d8547726481a7919b35ef9768a30f918a8d884238d3a07313cce46b33cf326ff0ceac23cb41a6ee961f7b241a8f00a7e0aac337c22bc4d13ca5061ef86aafa088ddac579bd38eb480663a7477dc83216595ffb643ff063a2bc984bf59e8d91923502ae39fb7df1f4c9857a2e0586d6915080f96a18d347d1fa15ef79cdec727ad7e3ac5790c259c9936aad76262be021e950a6da9f9444e88c859343d7b758b1a064f2763166faa12182e31a691fdfe1ad824b1ca05bba03e477295c1ff5f70aec6fc22a5491654dd48ee5f5f85174de4b3effad8db62bcc50a349ed5d3946c37f6fd96b1ce680242c5adbe757cf9a5bd729cdccc413704baa95372cefb5732a73d5b4c16bf20743846bc7a67b2bd30e1c7a234848d887b525e08ee687954e9a9ef514c5b11e91dfc428a157c0216b56937224f39500b00c4af489a64be2ee5f30e25018ddd1745ac50a42f2ce2ef6b548935f580bc8763ef7296043cb032bf69a791447545bc5292421675f15aebf7766308f01d1258e3dcbf0bac2889cecea2092b22f9a4a6405135c2df287450fa9b6d686f964e47cd9919c486e7cd2f63e0b1e5e59957ef140151069d4970ef6d553c9130998dc14e40264be79a49339211e675cdd4af3b4aeea98a4bb9bd8f3b82fd780948e0e629d46df3b275eb051eb1b90eff9568c71835a2b5282ae82f455bf769c6cdee6eb96fd578d860f34bb9b8529d932d157af2a3c358c68eadb2ea4ac2a9a376cd1d4eee4a6afd14dc4b6cf196ef4dc4de3d0edd24068c54ecf299c91279855b3316ce17eafddb44e71c20cfa597b6ccca19d555e5bf923348ed82e8ad2d685198ff90cdde8e5905a1f00aff5c938b0d9b3e2c1d2e2a327b241b4e43254ddd13886e30c0c123207145ed86942a8cbeb9db8f03a83643ae471f47d7bc33a1107942368e6606682798cb012515f312bfab1baf1fad84cfd9e2e336e6e1556b12eb803656413281c515301fd78818e2059949488637349a037bc86c0f4af2f1f4d0a4777fc0b7e0abbb6d724ace292992bd5366d8ceb458cfaf1431e0dc20238ff0e89d6796ec2a68d581205340516e117524c517b835ee83766e44ab81bb17bce48691faf52d3c6fd033d6997ed988b6e8b776b3bc002ceb33162444eb5041b149f0ba3f4f88078e2d6051bd780cbacfc2c33ed57d4c904bee128aacf5ca049009433142a5d62a7b2169b3510ace6d3d30b6fe658b9bbe4f666557aaf189a04170cd0e6ac510a6df54e7c2f1f6c2af5d8451fbaf7750dfcf035e3b3442364a82580234589e011940bfe190809710e8d144171f5d981f384064516d6756a07e1718a6dea8acbd3338ccfc887ba56d6fa419773c3783251eee9106af40cb72c107928a28159aaff4371cea784798c7a92c3872728957fc4a1373d4357f8993b3c859a213d0f2376b9e8e27eafd3c4276f28f6292eb02d41ba2175d93734a013885bb4337a1d744f19c0cea856b0e634db93bc807209ddb513314a375caac36ffc7fca43c7efcbd59e4fb5327a6283b72b04f12a3df57e7f2041462f7ac1b3d33a46ed945c3678b2f533d0d7d17c603112c44af37433682725ef593411039065e00a86ae21f32748944c7b182da5f7ef562fd88439183d0067c9f8bbdf8dfb88f5769b0f26ff821eb99466422f930199f0b9715ca9e20d77b168250ee9d5a2abbbf5ee55325ceb57e34ef10a1dc3bad0df80f15e85fa663e8cfbc505a6f0aba03643f9b05ed7f8dae576e9dee1bde58224df7365f17870f72c7002dd502890aef07d557b7d2e3c8bd1a38da116e26a4f6dfd47f68b4bed6ee8b67b5dafbdf0b20df63bf4a76a26041e3ab2631e71ea88b59af870eb15d11f6a616bad54191de4144682d861bb61ec09f3a0ffd86814e53705caf7342ccb5d10db02e5515f8ab9b746ff9b401c21cf065e7bf596ed46ac6abafc2ed20c07f1e8dd5ab2d0b578ccb6b7d29882f5945948b0afc8b22728ad2d915ef3edd546d97f3d9dcc5070f6558e74e3048888a1534650fbc80f98b45794ebea5696a75db2d9a9d741dd5e6b367186cdb6f46de59fbda6c212c5bdf66baa3beb1cfa772166d3dd9fc9bf63cbca3c97fc146bed3e6b725e547192a4479a6047e73259198f73c0bfb9eeb77d84a16953ea50d3d720e9d076cb8d249359fd00c4196fdf9f5304836c4f3058b3b7c01371154a36a1e52804d695e8061447aab5ade5e30912f49f87b998d19062dbcf41d11bd3f7f355498185c72f57492a614dd63838f48a5f2a15f80bcf5099f0e08bd0c0a2a4cf7bd27a114e54e02b0d8a782c9805519c8aea1549d5b40dbfb1674d0b030f6cd397d963d5b099ef8d88d4ee8f4de6eed1ee02a3d5443ee0b1ef32d350fb45a2a0216fccfefdafaf9b4489ef72ff796a8cbff027d3acd7c5a125d4d01378a41df3f6c57aa47f9ffdb1fca78b0d953fcd15eee81310889e9b5ebe13c2c46bd99fd8cc017ada266f28bbda05c068f6b832c5b0b4c6340e955bdfda9ea27e02211b59872331a93b518503e2d8414c1f510a7aed8e58f1e4bd64ece33e8003882e09fc7c9f5bfccda79bc718fe802bf7f57958202bd4fd27f8f992bcd7b9bfb42b22ffcde9e0a20d8e6dd5584874b0bb8a00741e566e2108d9198612f7bc2edac5ec4af6efb7cd045f6b5e4cff79b5bfc6134a6e6addfd0c20c7a14520b524e63cb868b5cdf7956cecd2790121329e3b6882ab448616b4fca36a3ca1f79ae8bb9f7a6260366793f8194633f5f436abd0975ae7a0c771533b3aabb32e4f77492848a23d3303eef596762b65a29e284ff4fab0200f06a34f133944c5510753df386c5c833ead6b8103394d58c3036a685808bad22c82680bac8e3d6c674738fc4e22915d1ea0441a7f4a0603e7a7275963132c8a08fa6f5a34147509ee94afdec10fe2ad508b483c110e345079621f46ea85f7b61703eda5c75094e3147d5afba1b4b64f05464ec5895fe38e473bb024702f6b941f464e8eb589a9c0e0a16f8158ce6a4dc85a016f4b00ee52cb5f15a31d1a49c3daf04be7181116edbea18bfbda2a406f2fc72a384a900e0ac5751795621bac2eab817ffa2e6599d58f8009f2955de63674005bf94776bdb528ef3814eb84ce094dc2820c9ae83b10e5e9099fc66be08557fb1796f3500e94cbf3c4e515067cda9d3dd713250f54a0160a401a56b0c5caa6831e8f897e045b4e106d45eeef6a6f4fea9f561de1d0b5fe4923173d6dcb6a676f07b439b5c97fb379b230d01619123b9044e6936205d39b803ec2b55f4017fbc3fb68b052e31a2b051416b256a20808443b5b3971cfe7ad8e15998e093a12d3b1a09483964ac9d2ea39f1ed0734993bf34e739bc8dd4e3959ff7576b6c3d8601327ff3d0da013eaf52135608577b81b6126d81416b19fe161d984f6c249e5c303193edba3540b52029279708bc05b4c490ca362a5350d625348a17e62125305445afcef194480cbaa831653eda7293d8ee827f585444dc209c40be6d64e049ac28d7a7f4815e8945ae84c25ec14daba002241251b173da3fd39426564eaa7a350eb713ab841da781d5ef9b6fbaab9888de3a847f2f79ab56d83c2585bfa33b2bcdf12766b46b84789d48a413fe218786e5c4515c0e460764404e3e141c8f961d4a6a1c2359195b30d8b99d3983671453cf5d14d44550a8841e04478ae753fb2a3f21d03357618f7a053a80259ed53ae9930726d741606db63715fa4633c6b0507c773e16af8d243a336c377aab1d9adb3abb65122760b13a7c8b63bb212f03ccdcf0d4b528851641ad4afd9da0be54617970842b9cf02b0c242876bc508a9a210682f1d6130499b75a5f8f8573874bc28d311f9baf85a2c14c6e6733121c8ee1d531a4902326fba1ad1d4e19e191897507d867523627235fe5a7bccaf82aa5c5eb8126ab30d731da2f1cb362a16acfb2bbb03aa9167b74a1454dd779cc83c7a1f71264b75a3579daf011d25c50732ac7c358c5d291fc3a52e6cf36825e01d8dfdb4ae02899a32c78c04111b8544b851713f43161bb7040f356d56aa22451d530a5c444818bd6c5f18cb902456b6ba183e74670337807cd753258d6cd74f71c04a9a031187932dfc0ce9ba2ad80299e396ce90e64a0669b6ffd1fd41e200228f16458dc0c328c6061d49d29641fe4a29226dcdfb2dc19547bfaccd0472b8695ab526104e34bafa846ed182f227bc6bde74d0f46a2cadd77f435cdd5e60b706b342e64153fefe7c70a35dc77f982e1dd34115ea24353041f3d389bcc1f34d795c4aebca3028a3e88c401732a1fb454f126807cd15ccca941e9c85ce33adb6610f802b6d6075343e325c2547b670d9b57a3a4fa873d6b53d8b587c86cc69efb5ea30132b03b7c08349e0d1f5913a7e4c37944690e9739a66adb74dcdac779301b6c9185e9a502167a4c66d33482f8f10901ca7580dffbee4421218c84d9674581a82b28e9578652a37602869dda87d931a0d907a49b88100c3975969270dccee915103f7421367acaf1852fd0e30c9b4472ca4797951819a0aed0cbc3b9d37ad0b26f73f0ef057498176d55c0c1dbe8074215db4886d84d8738d28edc52a7200ad7b91d3fa241f526541a610acd189f87453a4996602f41b921bfe0490bea02f9680412401053c52e0cf3ff96f96915aaee460651d710567a02933db718972a79323ecfa92cc6d8d361a8d806f81931ad8b16092675bf0e62f4113c6c7c23f4c03751c24ecf26114139383e8268168ab1e58cc4fe1e91040e9ee88b9851cd757e1a5895ef8c465e94ebfb934e7681448ea5fac5cf503e410b709deb98c77c6f2aa0c134cb41cdb261c21cdae430d90315c01cc64120caa4cc04146c9c4cc599858c59848fbd3400e28e46cfaaf3c92be7e24ee852c4437fe63332e00c39d60078e72e42b68c4e38aee20a431d4a4548661bdf5a856abb058efaf56dcc43ad094f3552cfde6e6eac98f981314f60beab63dd3413d213ed323eba884485e9ec00d8e00f29996e91a2ca2f93cca0cb31a3c20ce34a2741a455616b75c3d3da10d0865a77f663f0885fafb752abc6a1411ec39ad9c775cf2c5edfcc3235f8a01710ae28d68fd4beb4edf992395e5882180fb62559416f43c8f807ec8715ba74faa7f041607f828c41658fa4fb7501ac580f81f1c5c70e967162ac39959f2e9bd0697f56d626f4359dd425b969429467a0fc075a7d2c24754111625e922dd18f8686b3c290ddfbf7113b025deae432da7efd8f7420ef79ff80115dfb903bd594188afb4b38a5a60858bc7e9ae9873cc8751038f211b9640095147a37b1cacbf4e69eb4155147003e1ef79e37e752e7f80b737fcae0e4dd1ec178fd64aad8b4ea2026708fff27f00df1cc6c8afcc320c2cd503d0157e7359fa3732a857d2927309c6b2c7d1e4b1c1f4afc10d9f09b3765a57f354ef4fb21334f0f3462596312862a0189029ba6cca706010ab5e08f18c5c7016e00535a603bac458eb1df20b301aa496f7ad1c7d61839c818c58201633ac51d7c9df2496b4021802cf992443bd654025413e55538979ca0a9827cd3a6510f3205d202c69ce4bb1ec73738f094b2d9d47f0607ad13e6173ddc2ad8eeac58cd5d132758fb2c77159453ec64280b90ef0090895b6a9548313e00e73619230b31b3ce0c12c55a3a1cfb9441c8b215473d39aefa28c662fe82651d139d27d31974a9c4fb98036113dff67cbd4913cf773bf34a56ece4c1a0e1bf28bf89580a0fd6a7a768b067a661423be7b5e3531754c27cbb107543d8f27b17b7d100bd209c2861280f37276c348926fbbea054cf7c7c366661c4ebe3f5d241fcdb3994f2dee62d0a159c70a1a1dc008ebd41ba1083ec2de8e737734b2bfde6887f6705885bd3a4001acbfe12ab098ac432044aab13e1432cd4bf32bb64aa58c3c0309b01be7764d944538504e290b6d444a31785380ad48cbb1ff9bd0a48dcc51110ec07f67617c6c32230079e973fbdefa74867357fa5396f9b66ca6bdf801e56e29354dae12c8cc6677a490d38a3bf8a97fead9a204847b7cfff6d6d757ab8dfe3b1cfacf780b5efffc1bef060522f15f5e5994b6cdf40b2a6a83d7786aeb0656d0a654cc9804e8a1c5d435a003966ced5000194e01b40b0663f64c125c7d1ec8fc95e87c8c793b2e19b078b7d51aee3ee1fda579e88ad61302ce950404968ce9ec78f6c86dbe180c01b1a999d621077755c51df1c78a71e09323af7139aa6307c7698847d73be0d8215a80d2f25d46dbd5586b7176c45c4af613ee249229f625e3e02a65aaf024f4450925b1abf9c38c4888209cef6360b5a8494ab60d84593af397d11ce9d2b7d9e6cc81e9f202d9a46f0555713b5fa5a0b7f495d0994ef91d270f86069ab0a05ba11aa9d9f10b66a58f8b2730a11bb0f0378133128127a688c0c5e821b0856c29846fbce969890246557ff20310680e2d83ed02c60ae36c887004520a7c4342e4ab8206e6bc6cb05fe9e97bea5f457831129aa2a38bf0bc12878e18e3711e414d086ce314979850d8f8f17dff9da74dca0cc4be8d1e48f0803372328b16bf03d4cf9b3b9fd58ad6ad657ba7a86ed59cade8c2e5a26a8a6903da82139dea0c95b7d83a77cd121281021ad37f32ecc74069a2bcf670f213d679a73bfbc46251ed7bae70fd18c65525e45447fc20539eb83087f6141243843add558eb080b509643b21d1e986acd5c740dc7f346cabee96b9cda5fef9c51368b1302c1382544a879689da9c5ef4b2dfcd6aad1f1efe9e1f0b202534112baf81e66314914041eec9031959cdf1cb5dec3bd69ab71de48177c3889b1d6c0b5598c14902c90daac004f8b6dd96f8a4b5b8bbfa36c124abbf48755a857a09d1284eb1702b90b95b77dfe651aba593077b2a37c50b7409624d11540179f744a1294ec8764d5df51181346b7cfbe15b161490b387f852bf2e6e5d84225bf1fe9815f5f32d512776d392cf988fdb662c38ee7a0da119ef44e3ed5b21c7247fa0abae9f296fe5132f51cb56768653f849a52ea9b33c5c85603be5fc42c0cf536dfe2bcfc5d8ae2ec6d306c81f5a3d9732ed6b693102cffe57e6726562626fb23cd23ec16fe34ffa97af7b28e0b51ba7717c855e40ec5d0e9ca9add810e6154ab4b56663e5a940e809cd2da4ad49a77b99854b9e7514c36cc62223a20b7574d5629e820454f1ac4b68e0251bab893d8af038451e3226c0b9ec82179158ff7e60115bacca74575622b4cbb75fb2b5f1b0330ad5201019182ecf91128f405224bd00c3b45faf73b706f173811a74af9c58a49b7a2be24bd63da41f4eef288a43b0323d8f2f4dec815951e9724af53585c793bc6c2d3c79c7c6fad2309f5d06073632dec2053b1a6d683b63cdc833c664ea4f87f3d80f4f8a07124ae5d3806350a9e54561e6a5676ba2e74ddb34446b931eff8c44279cd2ae47f03e4005d49a3e40f6cb893b9d1a5c972a074d84af322862400871ca57cc054444179d9d05d006654fd029a6fa557ef4311386053c79abc5509749b6bf56c1d36b7c56dd99b74188be239908a647cf5e40bce1732bff0d44d92bd34600a756a050f9cdc11de36ce5c179c2b4b7c91850b019912bf7a1c7af46aa03c89b44365003c680d5baf0e98b33603578eb910ac83a6b499216787ce221a20b1f389fb050c36c7716fa5531172b6276e1c5b7b238b9b5b1b79121a8a3f7dd2b132a6d10c1674ae2f33a9206f564bda0638fbdf7dc0c12278b122e2f4a2abb0003ce028099600c4f935fc8e0520642ae8c9d5bc5461eaff5b0812edec5f8e16b68d708c01926eb53b0391fee3f197d2baa8c95408185060550aa72ccaa2f07da3f66a5982f02e2ba259edfd82b0f1ac37c7689b5e20e1ab6f219fee80d9f8c608487e6de3b68b40844411d34d48e6c90071b0d5c80a20165a26eaa0df1b40456103c798055c42ed705598a73ce72b16d50b8a74d421aeb4cf4eb513e270c4ce49dc426f974f50baaf74fe2776d646a7646b9a9a092d53c6684b601fe9e23d76755b163755291050ef6d9357bbd64df9c011ef2efa0bc1eb675742a6ad278490d0952f6e51e9d59478640d2e5420d283f8718663f5eb3765d26ad729ac186c52b10885883df0c05fa1e1f2d9885285a0f31c879d4830343e8638932c61d9a9b1e755836bf55b0270935daca5384f9f0664be32c333178170ae09393801648d731a14ae01262368efdc9db5a38c52efc358aa263488aba74f46b3058c2b8c63825dc04fa6e9ddfc513b98d3b44b7a5125cbcdd89e9a493e0d5d7cfe6cf208147f4c1a567de44dafcc374582b33b63ad591f93e1da184491dd34323131b0287c0591052b96f2724f786b247d0f2e84ef06b9eb3ec84ddbf007ff2a4050778bae8042822c2cca4f6960185d5f17706dc0dd0cf4392b829edf5f58066b06754c080acbf940dc245c180c3a24fe050458122cb86b0443a5c3cfd23d09d3fa8fb20921ca39224cee76c30761695472e486197c57c0d5f015dea8a2b6ef28a4ab2634dfad931cf542f3e714562734c61530be4755b5ee94a3bef2fea740127a12735652dad01429a27f43ab181f5ba3b2de00aaaf2175cdd253e1506cd94796a1ed9ddd91f77f6208d6d289d201b550fafc784e4db20af02b1ad7b9ab1d5211ef1de21c8dabbe8a07354e5df88ffb397dc1a33a35e67ce364b73788da56c9257f072c24faf20f7eec7274441ab255746108234cf4c2046cbb1238be01fd8f7091142ebc747dfde7e006dc5791d9b96d260daa023749c7203d97ec3618eb267415b80eae631ab8bb34bc285977bce8d2ca8754c9797ab24961d2b514337d8d53f2c0ad9c7fc57301bfaa06a77e13a28472bb10d719d3f4be902e86537fa60c277c4816d8a09d9c940863a5df4df47bd6aea08882a81d6ff8c7972bc43a191f3b37634453b9cbddb9b10b32b557104f042d0804f9402e364bff0e8fdc54b7c2e41d6bb2cf5110cb2088b71f8ff7f913da2a94cf452ab87e0af09741fdec670e552de82c514018ed6730440bdb51837c95c386a87783c34b8e8e45c7153e6dcc09d47d6b31f24455e8d06f8859e9648c60d682d153787467cfeebbbb0ef65c3131cdc4d948ffa2540a523fd5809a934f202849f67eae4258b862491543dc186d92523f57fcc7172fac45d13ea4fef75021c2a5474ed29dda5e4343d0b244d2d1a76d6987a591409b4e2368b27ecf408c2c75bd451571dbbfb2e06822b0da97fede35a07c4c9c3e0907f32d9bb370ca68b0c9a3bcc79ded755d83b0c1275ff014ce3229f88acc320abdf2685f479fad2e75ad36793f5f4191d66d69238fd2256de8c6f93be8ae220a2b88c5dccbf095e7ea1ca47a4b879c2d34150ea20dd17d87e5c78a03905343249ef57246c888c477c04a4bbcd7d3d779cd41c1777f223fda0720cb3398cfd10b83bb6e1c30822b2d68500c18e8ac48becdeaddb3b2dbaa21b787d4601dd3f754883340000e444fc21382a44b3f124230d5b812ac28347eef707f711804ec676f360416a36058cb5c96ebb1991acb4e1da8aa2999a19bc63296bf2744794ddbce3d8bffd1983229c1d9c24ae5bc290114ff3be4cdbd03aa3d29fd869cc713eb03cd1b4453374940f637536a49999447b55e9e11d8fdaffd283a0d4d078667ae816588470e9338b4cebfe7824e191e7f0df80ca7f13df5b63dd5ef96f411b3904e3a2e743cc51e9b1b9b0fd9b5d9ec121e7a93ac2bfc63b140c32bbb89bec79e3965b1362006e24c56fef19322967662eb5b9f87dcba0cdbe14c6bf64f1f6b83fbc2fa739e35218e0993e9aeb80bf511c9dcdabb62a27924ce5cc706c3eac16be38744a46a953338b2f0359c8ac9b4d6ca5a0d3037b5e1d281477fa0ab533840650ee9cff12cfef0bd0d2c8485539d4a55ac31a942038182e9d77f7f711f9b792ec03cf7d1ed63cd8e78712495279b5750773e7b3f8aac20d0097f5dda53f18e5e616b0d4930051fb2729a1b046bc6c0a732f8d6dd85723c0d8440bca9253a3c2940f75e4e1e32ca9d062631c83d3deff680ad766bef9ffb2e7193d7544691b67e6a071d635a2267673a62a673a11d66ac192836e84ea0aa2b7632750b2328504bd92451555ec73f39b6b766238336df17e97db764f32d3d2f215cd3ff079e677488012eb643b4ffc9c832300444393103bb974d946a9ba930df48beb29dad8578be8880617f3f00250be0d631490aa5684d150373159485c5c5f963fd5ccb57bca3794755ccc65c6173085b4ebc4fec276453212968c3fc8bb400ec6af6048bb882048af06937acc8ee07ff0aeaccb76471d70d11619bfe393f4b71daea63d38986245b985d772c64fb8f906ac913ba50f9c8d1d0387651cdcc2538fd9ec16f5b0949cefa4b4f0a4dd4021b0426453596345ee61711efc93ebceb4b3b67d47ae1bedab861563b448b4c6300e5ce234ccbbfd570e18156779e71d605bd3fb0b6840142c7e51064b70385d6dd7204bf15e6775db993807615e56aa99f471a10fe5e92ad5a4549fa1461819933d8baadc48e56ade8bf7e4a4ea6d71fec0ba5e1012043e94407feca5ced1961e3ef763e7e403a4587c982b1033698f2b6bafb940211c72b8d65754d38f13121e428fe29370f72236086477ad78e1384b9eca7142c1a8daff3a6e2596d4206720da8e9b501f71dfd938b1ffd40efd2a9751cd1ea10bd0a0fb0945ce57648d68e738f72dce8a2b2d01fd1aeefb9652c11b521b574da961975418b6b69cb9494db043ddab31555ce0897821e600e717765c33522bfccca9903c19efefc3a080cca3a0f4ff2f0c5cb0f4140f655c1774c23ddbfe3d7f25e3fbea50adbc77de831e463db7b96cc65726be8344aabcab0e8a91ff8f10cce342f181648abe15ae2fee8e449d9ebcde585c1e54178f3073f881b5c847371d2b55308b039e40711c4190c1336380ce6d8710c8b06b2ed11dece1ca0fb76030b6499d8e45fc8fa7bbc13a4f71216b76ef050422e9fa901767c7bc97800312fd1d82dad5886056c136354439550de0d36024086b3dc8528a332e56618f28d0e6889a84dc31d4ceb5b5c1baee71040c792d86f58a608e22a5fc79233351252cf327def88d16a11de529881a9b5775b6c061f49247af68cc8fdb5eb1b5340a852efca79e39bc30bba78add0df49e488f0fea3d51af0feb3d91de1fe83db11e1fd07302bd3ed473623d3fa817fe8d70015cab039a7ada805820cec24a074dbd1637e8db2b83ba36d027488e20d5e308beae2660f82c9090be1dadefd38f240f9e9db1f153fcdf5349c70528244f940012e4c29bd2a3679ba79217cfb4d9b2ef08848899434812b2f7de446e29934c4906a108080890073cd880244aeca22a8c47e4fcf3c492294fdc7eb7b7eb5e00a5f3ce6be71b2891ddeeaeab81a4c90f28a872650b196c2c4de1850c134a284dd143b295420c26576e574a29a54b799530e18005e72584f51bb158fe21458e1ad6bfa5611bc0a3bfff037f0a70c321e9012155c4346e370c8e22503bb84290a8a63f6c4984d0eae377108556e091500563ff84066030362c24ba9c12cb1d884072859c32368c4f22481985b03c0f685853092588d030b9a161b9f24b2959729d25d11d893a04db7523c99c84b3d1c6105a983ad4dac2f4f171e2ee6339830e8e48971d8719743658ede3ed4868812f832e64e84eb06b580dcfefaf7dfdb476bfba5bb5af5756f48ad2f768217e75fcfe694fbf2bab9fdf15ef1f7f75df60bf8ce2e4371f71bd839ddf244c50489caa46cd3170213b33b315363e162a2845c145183264c02813a7560003374c90e012c6cd1157e4eb151d943dbdc5f06b893b51b384f31a803368c0e276bfa28cd1c4ad8086d96d37633e3f05c32097bafefa9c60e7d2df364cd24927c882df22b35d10b526405148a8ce48926aa2399de0494dc2c2b5ae6cb5c2afc5cdcd913d764a9fd648bfd86f6904f12f8a40f225047f10295d826101ae8c6d6085200ddbd2b06194aaa10c185a5cf927e8703054a266922e5f1f93a6e9fadc38c6055f5c0358246090783952759fd25c9e5d8ec27acf8a61de1c71c60b1cc448a15af9708226283386c0e286d8593356feeaa3788a67484c5fd60891860c6dd0c4e99133c6d2b75fd4561a4bb4cfd9af9972b07ce95be1d1dead8ff16ba47f7ff8b7152431566b432eb24cb09e59cc6216b33e2c4227e8d51a67313e6c8867e0b7c0b75c2ba51188a35774d47db143946cd84c2d7be7eb4e2e7eacd73efaf19514482b4e9c38f7b9ced3b4197ff5130ce373cfc9cefbe4d758645ec97173ceba581ef3d4307e1992dce88404cefde04627245097b1303779f73756fb4751fbd57332e9cea4293751554f697a4645354bf354f551667ffc56af11798188bc128c112cd2390ec8f2248c1b9d6498c2b32e367e61a4e2aad85904b2816711883fa78c65a5a9a4c4c4cc542b7f9c7a85f66a317623acd619cf629c857fc37a14a9ae8c3db38cc34d3b372435f972ea6818bbcf95eef973ac366ee340a3ed7d0373f8d42b4adfa303e9171be61f247dce863c094edfc1fa4876a7d5dd3522314e9c38695cff16fc56d088fef4417f3ac83f2618f3a94370fc832fd9929c8e4c695918a77232626689f3d719d8dce834039a04dce8340396db4e34c8eecb89062cb7bbb9568e0ada034295305d7eaebdaeda87d957cc49903f47902fe5468a9b4e073d89cdf86bd2a785618bfcf8fed303e23fbd11e41b4dd0829593ae6c249d0d511c71060cd23445618401bc305285d39b30a6fc90c21b2c4038b1060a0a59114d42a8a08c2c090b2f8650218a61a10a8e1526807892040f5e040a667c4102ca862356f0e40535375071048d18ce88c253d0451070acb8e269862fa8549031482166c105142e40a451c3b40513152480a429cb17305fe264f10508072a6e0843090f52ae48410707aca4008397308aa82269ca114978216208282772d84245c914153e38f2c311355232486d21828c1411569694686a83830a114a0c1459105e88c8b2c587401ae2043224692203032da050408ca5a732b6b00163cabb2001214615306ea8a0d2848c0a0b18f3a68b163c51d1851b59d0135f9e805aa2cb1a30b0aca0c1d2992a284c8001a5831000b0028c0c6da2e8012a88146258220a131534747144155c5a600514497ec8a10a064d56088a1f39f092a20548c4e042050c2ebe05055254d218424c0a6644c4a0d2e6ca130d462869d9820549ba2083ca155108a1420d4faa8cd9e2044f904122080ac880258b2d9638d13206016160b93244162347040184d3165862e00515a62e5e5400c09b2454ac30419c24315936587304124b901155c6970a6411668d0bd2f05085112c3b4e67c060c3a50c324f149da93244941aa09466b8125ec1c30a7c0083260635a480a444061c22ae5062a58a211c0538e03c6103103e84810ad3fd8ea73adb7666f0d065072d9698a2a647f8baab2c5e1007cc9724b210d1c3dddd7fb8ee5ee7e7e3fe802c6450c060420c0b989c9a2cb915272867dc70e2431247b840c19229ef14c393fb373a41c1c2094a1431c926723181a82e9600238a8b2f667c30e3c4142a8c880367872154d8f134460c383cf12107a71ed2c8aedc50c6892c905ca1428cc2cd114a34ed10c68c35ce43bce2c90565d84851a60552f494616202198098b2051967f22933c514a82e5610058d1ef1630a4eacc06e7ec65881154b584c31f5822698297e54418c982885b41c5d277b1773b44f6b1db4baab2bc13874997dae0fd786757314f452e50d2996bfb9a59b1c267568686888abaf9fcbdfe5ea1fe65c663133357247c3fc1d8538e55004f28ebc4204f20dd860c3a130a2b92e877ca0030f441060fc81bfff5124a2ef492ca8e6caeb9f635698fbcb3bb8eef4c80c367669ac6134d2f70eba1da5f4230a11e80413d8b0bb2b46c24a9c44031bc04004c104fe803ea56fe9903481c65ef42315d766e0ab55a4aabfaaa0376c3e472239a9e09479f3a689372e576ea561dc50dad40febc3efca1dc9fabccfa261ebd414dd9e822b304639b5ea33604b354c8a80044dff888024ac3226d1e24a1108ca862e356625bd542c97f9fd690c147d7e8f221271cfcf7d445adb177660c8dee491e0be35b9a63da77d3e73305226bdf44f939435a1245fc87b57f224fd23a756dee4b319b6e7e05f3baeb4b4f26fbee2bd8493d441550a79a09c925346530db34f567ebcaea583384a55deb43368b68773d991348c05e724ada561ec4a11888d3499f4d24146f257cf2f651dd40426127912bf51306497922b77a986b1d4a6fc8c1936ecda8a94925407f1c829d8546c989f712d2ee322fef0bfa0967199cfb8fc47e4992924532f36520d4b4db1e4b0ad5c2d3c6b1898867153c39849fce15f555ea20a39b8503f605897336e9ad5b7b5d6aaa3617247c3a4d720514a59e5b681213fa016c531745cf90171834f83afe7e3f9b8ff74befb6c4ef82d507a45736a5a97a3f3f68b77dac8b1dfb09d0f632cc1b008bdf67da258bef6873200c3a1d80e18c65e6008bb21eb5a384069efebc6732ed7bbe6f3f6357e6ceffa6e998deff8026d3c0e30b4b2a7fa4c4b2db5d4520bfc0e5c7290ab6c50d90ed211b25dabb531c2ae4eecea0ce93c7b453a9dd4b55adb810e0edd30269babf1fdf84bdff535dff9f1bdf1f1adf1fd88b7f536be78739e9b5dbceefdd85aeffae8d7f8b8b5883ffd5fdc7e1d30ac64bca18a40fdcec60d5751c964526883d7e0c3da23fbf0d5df4b52680350835a415ee201c3d70d3bb0c8bcb6c87213a744eb73be9eef1b163677655a50828a3241942185972b5e082926d0224394186a60cd8cd5be7a1f1196521699cc3df5cb7df752264db989aa7a4a33a39a499e9afd132c8aa594fcd5e788bc70354e5eadca55fff68545e8d5406ee2ab31952290ae54b9a20a1d7e4083872718c841083654634cc1c407ce9baa85e2134b494949469a8d16d913a64a32e686ade676bb714ae2e58612895c72c9b20e72bacd3385aa04505748c98c68a1664d8f4ea1082869aac8987803a7477f53f5779a213d4b3d8eb4d3311670d1470a94943152ce4caffbc1b7c626c4f890d4834eb19d04bae740ba6491aeeb6a5fc37a698cce0a8f5cb7d6187f5660eca13daddac70854b5a37be4ba73c710d7a66ad663d3f1b0da334cdc8903b8b5befac31d9767a6c34c4ec1abcbc58f659618b903d8b113c7f6eca8377a310323965fcddd3b3a2435bbbc41ce5c3940f9c40bbbf13330629b9b59b692a258b9eeeefeba614413d5c8f61e1dee596e38745f3d63703112c55967bfff9cfdde11cd9a3802ed0b34d63451064d932d63a25461460a1410d1c41764bab489646801861733a67428630a49cb15a92c4ddc5459ea32e64b14d306083763b0592343982f566c806345490a6250831354886982818f335b9ad4c045952553585ac060b3458c23d2940963441962d874d1c3130a5220065406b2a491240822aa64a862228837606ea0810728353b64315305144730d9a10566f830830112125ebe585a326bf2e58d143c2c59c2e5cd0bc26862892c6da214416b5a40d2e66929ca940bb288542c10e38d132931f040450999041306155792a431050592113d3cb1f1a24a0f4d7849020b075928c07b320176cd4dcca576fe1a3d4cf952f6741e9a6dab885f59c41f88a3ebd58b70b3574eb0b9b9db997bba4f8f605c6b2de49e2e9925b717f41d0c27bf4bfef4aaf9d1ebe8d56eba1f31f7286b7dbdc009d28f8f62324b8b893e7f68a08c2e1be33b681b268bc8eb3fa34b1ae5ec33e6fc413f82e15cba51fa17bf460f363c9a4773e97a8c3d264e173899b061779fcec92ca8524a194197525ceda09d178e1bfeec365c3574726c8be575dcb6aa1a9d2e9bdf775e19b082fa8d64c3c048b2bd281ccfae32173edde973d5a646416ff797d47dba9be93238e919ecbc2e8e5bbf17f5aed81bd7c6ad5fadde95d8eeeeeeeeeedfe6ee7ea373775f551ff3a50442826bf429a0ef5ec1a8b1b3c60cbacbd8943d123ea7524570b58332989aac1d94c1ce7d5d1cf56a33fe8420f046a89dfc5c97ab9dfc6a5c6ed5c94fe7721bc775f2b397eb3816eb72ac4e7edee55a9dfcbacbd94e7edce5723af96d97abd1695fec21bfd5e56a74f2ab977375f2d32e6783c3c1e1e8e4e7977b753b5dcf0e4f062c097a04f29f24982db3a0f4d60fe7dd7eb2e673b32a09f3a74baed5fb62049adf7ddc1168fef679049abffa2ebdf3e937c1d8b07a1d648f650f5cb4c04ad3a6eda0d5f3ecaca6d55f7940e8afbe7a2368bfd264055d7b9e40bc11fc35309ceff37dd2395d42c1712dd7e1b2711dabd5aca353c36d546f18fbb8b85f5c9ab9c19d4ea78beddfe9579c32827b7a74b658d6e1c23a5c6c98808b3e21cfed79f49cdf95ed392e5ad52885648ff8b1fbedb747ea315f5b45a158fcf1bed983dbbe212ec81feda9b8418b77d150384cb9da8eab758fe8f918228578496a1ad357c209f2477b0e220b6e747a82e6ce20512b540d6dc0e90635577b1d1dd45afb1029c44a55d69e80a504261f2e48210e1c22851e267fb4a794ce15939333554aae6e3ac358acfb0db4f267689b45b4cab16c967c1b0c4fb8ac24c6c61eb107d562435288fb2dfcda7343be880bddd465178650e0354d607d0b7c817b1658e58f07beb07d07be14da5e7b9814e25e0dd328a5b3cee8fcae706fbf21f923b9efd508d03e7a25c4e48f86c586dc5f9086695f6badaa433a288c5445b4201d14dafb392088a8de341cdc840c26c66e30d958ff68cf7d0c22853850d39ed3945a882669cf4b53aef6ecc50a83994949da132cd7899a1bb296abb1ec6a3f3f075c81131432142668b11a38d8f7a148b26f1c996106a56e336c372b7152146ed641f1a5fcef8940f25faf57adf6f5c3f522c25783082459e81bf6f468496ccfe51ef25ffcf29fa8b04e4e96aaec1aa6446906d7228a2a89c104928a6ad6581b255d6ded2c16f953b380423936a4031cda428b392591e2cef99d6b4eed6a9144e9fc9c28f9fcbcd1a909d55dfd50a9e4976eec76a39311196ee823b7b0f546a724342ebf908f2653f227defaf5f40f3fdfb8d1c9c8194d92dcf0d5682a1763fc18dfdd3dc6d9710c235657ddc69c477fc1e2c72a7fdabaf69b07448ef0b36aee6037bbe58bce9e1ef3282accd97a130d8d135786b9f239af484e217d18c095bf7945d20316b4698ab3658d1459f408b2a899e20353122d3ef4e86e0713c6527777b743018557f956cd755a42e24a26345cf9114ac2c2684154d5143a38b18306945230450d0f3f50597124d62c4929a54be94337ec969081c286ae080f35c8ae086088d4550ad377fad12341a3715eda183676bd11e098a31437c371774bd9fda33d1fdd7e5b4a8f537677fb953c3eea8dbec4c5b882cb2f5e1eb2c1d011c2fa73dd52babb7ae6a41466b5ba715bd8e5dcbd87d5e970b1f4599e8fd5ede1d2e96cb13a5c727274745ceae8e8e8bc5a9782e105dc29a560f86376e3ea0a2ec6866e85e5b01fbe6a58efd95f3606791061cdbd3ac23cf816eba3d703e3acb4e3bc07749d06dbbe59e9af3ef975a80ee95829d3a4763dd574cf5d6b8bab8e8b41ade72fe2ae76dc71afb655739168e3589edd581eb76d5f8b8b44abcde3beab57d471de77997f933f8f5c3d67173b76dc5980cbe1e76a3133c72ef0c1285f14465f0ac557f8bad4eb3eeedb3eaac99fe1cea5af7d473baef4392fef0092a334a6872bdfbd4a9d73eafd5f67d8d6e473a55356d662fab8942ed79dab0fedf6d9cbd335e7f394f279b4e4332ad76afa31ecf1597df872b9cff6758064db6aaddd57df7d56bedb8b1ec16e4bf9b93c82dd1d282f88b8ed75d792096eeeaf99999b89047932454418724a723454a41b73d415f1c0100bc6d55418937b949d199d753ec56b0e6bd1ebce2ccc3192436774a60115a2e7328843c62561430da870c834a0a2333ac3218331b973dad7ef5cb81cf7914160bda7c4867c85b86efdc9712c66666666e6efbee9baf50b4fb8b5d3de7bef2b83b0bdd7018966803093e572200f1021ae6bc460e82222e354dd0d3c9a5137ec408e6d45a0c9f23a6eeb408d40f33bb08a40f323687fc8abcd1f5e5df7c40ad966cec44cacb4c45a4c13725a3c29634366e2136891060536f4aad27855ccc44c695eb2384155b39e1e1e26a058215436e361420049686c189bdd8ecd7a04901454c5d49383977c5aabbd71f8c26530ac3f9f58be2eb442dbaeb9f06541cf71e5c74b3b6052287677cf4fd77df9dc3999ee0aec0f86afbbc45ac2ba96b8fd5d9b353172c8dc1d8ad71cd6a2911c275608952161c32d6a8b8a443d54966384ca78665ed8500049b32b80a49ebb4551d916c533cb218b8383a9eb9a6a888be3c0edbbbb3d8f1ec2b7b33b4d15725d76a25821db8cc5840de3368b55705db659a7f4f2c1861d95d2eda8866cb34e0907d33cc3836d76eba5dff1e5beebc0f0689b41b17cb9ee30c7598b7ceb6ab56510daf862cbb191831512a7aed19cc2865bd4eddfa222514c8c0d6dd8588a44b1db944efd28c0e56d895b3fb4f1c5fc6297c1c7e24a63c30ed2dc0e62d7c652046a1b4bb7350a761ecc38a662a494fd469201add5479f703a83c033c2cb38716e64dd8eb162b1310681af7c03ec883c31deede57b1a11a45017f56d5fcf8b7edc0d5c0d767ecd91ff52c87b290f1085bcbb0f61205c1921c05fa70e183b148363e76b565207dd0a21f5f077f7399b58fc0b5d39d5231191fabcfa1adbbbb8b7d1fd0d2222dee360fdabf53bf633584520fafd197c75e7f5511cdfbcf1b90dd7d73558070c2debebbeed5b7db55ed7fce3f8b44476437afb69bdaeb59e88afb8fe2d1211e9e7b8efef58fd5e2422729d1589885c8f444468ebe308449ff57504a2ef7d3202d1ef3eff6604a2bf7d3402d15f7d5a04a25f3fed5b45200ac61d9e316819b9b8a69368d374725a2caf03c11adaf0e8e8cea76c2983165c7d46fe527e12a55b82081d149d6a90bafd0fe820e6da75e50b91999d9500d68705f09deb39fe1c85aafce9f7aeb23fae62315808ebc0f0397013c1a54f6bac542c4117fdd09ddfc1f9d29d32b3df091e75d7c94c298f8c01e5d163f418638cee1e638cdeb034de69f2e9cfa9b9a651f0a8bbf3ab26a53182ae4d6845a54bff5a5f2f5b411a935378f41863f418dddddddd1db4a25d6070dc13257feefcf1c79f56b76de33ccf9352e674d67349d9ddddddb2372a74645ccbb2778db13b76ecd831ce1839c6d76cecc831be4e11d17a54968297c51c3687bbd8dd52b66cd93272dcc969c37523497e00d7c5beb863e022171b8b74bc8495c1ee8b972fd68b0dbb59f90397b511cc625d5687086b85b0117ce90c613516ed7670b033976f5dbe43ab5e11cb6e57b7596b86fb584ae92e5dbafceee62b5ce4963e5b54ebb42434ad565647d5252e36d792ec6a297d524dea7491dc59e1384e82a1656eafc8ddfad046a037823502f9471df10b637352a7d53badd3748ab0b5d6ae6259753a32ab53840debd59159aed39159aeeb74bad8f85d04bda239edeb599d8ecc86f67a6df9b460fea3c7da9c9cc9e9e8c418298d34d2487d7ad439c2b10e1096257773338356a4c03949515344531818dcb0d214563a338605ee15c60c3aa04230db6664b46cb32494b0cc6474206189534e53e29415cbdf1d2014411495c139a2329f46723245def8a4841bd78d4e5092a8373e1d11e5644bce8d4e50b4cc392318c7885fb99e0f0a4eb3f13c9af376a26660f2e49348bbe9cd6e3437c8e59a5eaf5a59d6c4b2d6cb8f9b8accb0652d8b9a1333618cdc661951d4e206d90ff99b6421d2652f4d1dc4ccb20ef2c2c28c0de96032d4410cf28c4095305d6eeb38af88fbeef3fea3d6b55aad0ea25a694aa6d446298dd72d254cb79999ff475fd63c0850254c1e09fef2fd3d9ef287ffecf2e9e0bea1b6dc626630f474cc96eddefb7e1dd57bddce7bafc8d3220a3d19b7e3e5b8468d1af22516db545a9448b4009645a21a1f85b53f39e6868662b12b613c3d1fda0d5e8221f773e8c51f573e1300a83f2000c74610356554e27f10fb800040393ce8e00716236ac37130b8793161ef10cce4d7c881a50cd3524a29cd50a7be863a7535ee5ea4e6f458afe92815c492d0fc6e823b2cb06c3cf0a07b07db55195dde0e838b5555d786181bdadb45a22f56c990a5a2144ff507a59191e1341a6c34348c3855d1c24e54d85c9e1b9daa30b9de8d4e559074cec5539c6a82a2b2fe825b718c31c6186bb8ba46f2dd844eea31297bb7e4f19faf813ef8824a431d9330ff1ef3d33e2abd7acc4ffbf94925db63be045b90f78a08e6d34fa9f698df9e099c94d463be44923d91b0c20a2cb0f0e951a5907f9457e2cbda0d80f095ffc51f7b2a4347d571f9e398101070f9636d80931ccc343d81a58c24c8c4d853b5e96ae25afd0a0c7908795da3d58bf0fc5f031af645ad011d2442037ed2e7ea156d200f7501f4867dcf65d7aba76a93b694425ed7483ed2fd86b94ffd62e7d341d29b55ab5158665d97a3614dc150763afaa7630d8b5e510e9f1072b8eb700ae1e698c923bce4de6ddf71140c37bed2a95318af9cae4009bb9dd7eabab55bba5bb7da5ef341e16a91835b67124ec5552b0f5f62258f79020a1ee475575ff8721748e5e564e39407394187b9c6a5bd9df35d07511b3d94f34b2a1d726d3a3f7da8df06289f3e39a66192a9841955575dbddc90a953e57de4f758ec02d661d679fe960ec86e6ccef74a4a923bc831217351c5a1b5d65a0d8c02a34ad0b45707668030b383e512aedca821830db98a4d8f8b4afe74947c6ad8042553c3a49516ba0182107ffa7b5c32bd646aa87ea6927287084204e207532291cbb0a1a2aaa092428840fc892544200986d5ca9d558b1bae6a587f0e5646754ec4a8e853e4b20b97b59e2622843daf69472f0d04325d90de73f98b21ecf6eaaf9473d6e90ef200e1f147baeea0bbec113b9f5c60297946d07e6a600326330a5d910e7aa1832a23d9d1813b1ad67d7b3d69076e600368d55c08a3d4102ab1ea1f068bc58656b26a3564b5f2d5bfd1fc8625a943585b76c474d0e9c31a0472684a49aefc1795252e89f46ea78a0d7dae7d1733f3774e867cd55990737ebec7dce21c90e1b09e87749fdbfabc48e5dd37c397562da8edd8a1699a4f4f13195cdc6fd84e15cbbf238b0d2592db0f874dec1d3bee04976e18a93a898bfaecd77590e17415cb495509ba724d5b813d4839055d7bf5d45a573d48d5fa9ae370429078828e2027d41fb55afb02a76e3b0f3f92fc7846e0d004e6f1d1970eddf67cac2e27e930f534e59c73ce598f8678e4235df948377e8c1f5aaf88412bc1d087a5d41fab3b94a3431d627b8f759dab729d943dfdd3ec71e0ab0cfbdbf60edad7ad462e3bdbfd9c3d127c1d61bb86c58eb8773858beee5af44bbdd69a0647398dfb31bd22ed6dc4f9a3a12b64e81ab1d6fd77d3f3e11569cf0919baa108d7c803c3a1ebbdff0674bb50c7ed3e74ddcec6ed34b85d006e67856c78cfbd8d2fe7e3aff1f1b7f859cfaecb60e8bdb7bef0c301dcfe29c9b8f3e9739fdfed8b577b214337da9e337cadf5b78fedc6b92799b7f87760a4dcf4224ee3a66bdab7bdf5d33a084cd0bd76520a190303d87e4e722fbf1efed83dbb94b85e5b78f5dad315a594993257cdf5f26d7bf68ab698b6bdf6ab0d8c9c57b4e2557da7226af5fe311816b9b2f2e5f7c26afc77d5d9add6289ffee9dfbe9764f0bdb04b347430e46f7b8e3b28ec065a704363437faf9f517f91c6dda5405cfaf7db88d8e5569ae62ff0d5b4e7d1ee40369752735daebed71a61ab2277e511b602226f5d316ce5976b956082083de70e1e0dd3c193276f0085331a0afdd3af81216562431e1d3b44b82197d0b07e13a4eb46af2848e7d6a53c725d0a86dcc595200f101e1f9ea116fc1af10e399dbe06b6e03782a09454ba9c1281e14a1a7b59574e13552d275eaffee9ed4317aef602f76af2eae1aed2d7dd3e2397af9ed56ae555d0e78a75594dd334d09370da470d8c9aa6f99340a475e77791a86acc6fba0f394e8ae5f11e293e8d7db9c53382f7ac678121eb3d16eb0bbd1ff1769f7c4e364884e3f8e8d5c7dbd42ba254bcdc9d1b9da4545daeebba4ffee643f3bef047bc4647fddecff74021fdb3887a5fdcdef37e38581bc6fdf6c9db7d2c14719918294c37de7837fa469bf4b6efb46dfb7ae571aec360e4d6e7fad52bb7fdf6c50a64235023794cdbb8aef41a89b6a7bfda2251f7f4b948e4bd17895a4f9f1589ecd36f45a29ca76f2391ced3cf8944359ebe0e83505db0c8e487d3c811683ed732024d9f54ab35c09c0824bf5f27c77eac0824bff57911483eebf33e2e02c9efbe6d552390fc950e7f2035902da8c3920c8e8d58a91b76ddc749363a511973f949e01a29e9ce475ae2fab3c0e3ce478a40d168823c232ce0fa2fe0867de506bfc1eddf200467dd7e162cebf6b338981fc6186344e238b7bf1006233d3972942cf91ec78d78802ce0f6b310673782314eecf638b53078e84f5a9975c458c98d83f67e70702b4884835b9fdbba1889a60f7b8970958fbf4498ea560681198436c26dcf7974b2a6b7ba0881f9dbcb6ff5dbe71b7bfef50be1460fc8fcb0a5f723845b412221dcfa9ce5ada5b340d0b705aacdb6823e0f10faf3a95c3d0701faabe75bbf90e3dc1ae7aec0f9f56b5ddae28ba4311b21938ae21ecdf0134ce7083c3b59e570aad8c10c942a7a7ab15dc37838ed203543da3d597f233a5fd3420dac1da199a8048ef6543909d1d808000040019314000020100a8885229148301c2782547d14000d7f9242724e9b0a84498ec32886829031c4186088210018438c199b991100f9d2e0966d7b80bf87620cf1ca882827bae51464d18a9364808a6c8feeea19badcbf01025721092335a1a0c3a936588296b992a3afefdab4f9ed77634a0d9dfec9c5369ff8d122c1b9953b11e4297091d14df5578527a5f10332d7999c6cd65837838a85bc0c470e32b7abcbe1e530b5e86a9c7cc6d6afa301402dd1fdc328d085057f7a089410e92fc54cb3bfa371ff511258975f54f6e81de66928b1b9d3698c9dbb1f18cf17cdb88ff58ca3c4ac52727b21ceb3ce70fda463e45a68ac7abf223a78d7547c7aac9da2d2330c3c44c821413dfd1f9925923900a60ef2d05cd2996ed33d3e9dc4259d47b450672e11d26b90aae9c7e966b29086d68ba1ae0e30e64170ec39e8d078603985ccd191b0aac929c2fd63f85776ef87c0024ea528007afb730bc10061ea3b324523758a8435757ea655a414ecc77b9d44cdc2a515ecab091f4c9e4a7f69a0fc29749a5ac18c9e10c09dc3a90d6a322ac22d4943cf3f087acf34d386264ea1f04600f11665351edf5ec9783ea1a197035de640ca1b63e492b2e31391645e8f470f643920ab2ca94750979ddd40bdc12bc91d773365f656750d137d02d260cd01da065e9e482348115933e3bea5f6e5d965e2ebea4faf9c6d2b292bd4e072d3f4d956dbc84431680f876bfc4554f944915f469903fa59a38b1e4cc2ce5d1417250fe936d4dd9a0bb6458b1378f2c670601bf2a72774de07441b5e2044d7991c260049ac8afdaa9bd68bb56daec75dff0a9aa4882aa47ceac05de874085dee94c1a08bf125ec73202eb7481027d626dcce337271255d2bd069ae4795ffb4cd6e4bc5725c9a79044e7485e5798972187789685e55770caf92d69bbab57504ffc7fd8e66b9d47c7c25dd5afacf366fc7a6680f942ee42a3d885b4e71e9544b34f4dc5a7a75b58ed8971f37c2ff7223c33d4fd9b899a9af5f410112cdca39cb9f5225297a7a598d86aa8a5b59a54d95c82de52f98ccf708b905f4a929ca5fd40c554c51a404375c572ff71d2c7f959f5b7d6d2db3136673489276b16ee04efbdf69128ddc8ed22c9fdf7fee8b261cd43184a106292b84a1df8fd44218de3b0b9a8296ddcedb8e2d7982810d407818d908c8364283d2ba35ac69c33df41a103a8cdb28a083b0d10eb2243d69423547501a32ca2dc86802bf99766db955d07c7cf41bb4c15d476d9c22d3920810a619c411bab78a3e703b0d288321609f5ad88adc2fee4c8af6347db640b17cfa84f5fc98a4d37eb708c37778144563287184a1e0add8ef536f037e8ec0544d872b326d9525e9a856292e25844826f41e64eff95b4663c3a13fd964c7a2e3fbc97b27720e701b1b8ce0c50653c8dc20d6db12426163a69cd9602c038c5a991b2539f6c0994a3378fd0c9859f4e6617c8ec1b76593baf31c4ca65baf3690d35f538bc108cba63a156c77ad1b6b7d8492729523886dbd0bfd4b17a58db646aff58d129f4881774b7eebbb9cf033161a649332bfa11b1412c13b1518346da0f033e45e4760a4d5d4749f8031d676b059e1b198a164f7e3aa6f88029197c372d2f00d481aeacf3949185294f43e967158f76c12d1c2f2855fc92bf1113018c20680b8ee8194368c54fc87c480d15809dcefc4c79c36c80c65c88869318697e634fb2a7245ec1da81d537c5500b181b461b6f9d6a3eba62cefae4a12708e227a12d4086f362c9dddacadb28c1545f5e90b2a579a44f1c6cd19da8b99e53cc1380113836bd4d809dd517231864d37448e35f0ced48b223629eb3623e1858b86485ace7e7e445ed98778b17670fab3edceefdb468dc4ee73051e5ed5773a0a78b9f879f9c3279b6ed7504a613948f51199b60af50b18c4b359d8e365bc39e6c091f73f225a489d2290b120399a418dae3ab1801ec19530c58ee40a8c111e51d09e7c241b598045f52780e6f8f255d019f78b38fa582cc69595b56269591e0ce04656601a1869f792009631f917f18e9911c74622e421a31a854d9edd2180fd118a29b5a535827f6a998a7aad84823d62bb1227b8f03c4dbbb61e70054cabd2c2ce5a3c994a0e2ea746a9afb72da53776e8cb2008b57ad94c2b606215c750a399f886db6bb94ace603efeea2d649bf885dec950ae5dbe2af09af0e47a52e4d774f17070685215c5a171cd8b54428cec9ad4c6e2920bcaf4bb6030939978ebe478577a9a06049cb03addf8a2fd7dd032b4c539a67bc23cb53bab896363f500c7969a9ff0662eaebf68c11537059f28fc0ab46aa3906a25de1a20d75e3cbe3cffa292105b0d8d62c71c75e57509c87c654feb186d6d5d161134cd3a9771b7dcac7b7b3b02083bcca3e94d9d584c056684b07de9c7463a177ae687d89d7c163ac664692de60a2d857c0ba365ef1910b86a3c0902de74dbefd52c0805230602b72392d1b1e81bb2ea60be6f6d929f3b168b41daaa53538de4a4989d930296965dfde1d467a3af1fa78ee3d5e38f6d2327b9fd6b0e9c562de6fed670969ab95c2314d8131e620c59b650b28ef1ce0f3c1ebcaa98952905a36457fb14aa2333e034521f9b38e051e86ca3508ca511c15d5cd4d1353e779a68e7c71ffb7137c4b0bc8a52c2740c4d6f98133b03e3224ab802470dceb5a66d1a6017c91d449ba1af23d99e70342cc0e5696fab5ae7091a0a74b6e7881143aa85c57bbc1a02ba6f9cb009b4a299b5fc38c1f14f355ac5960747d429cc80374a06ac659e6a6c74ab09120b0221ef37cf48b0e0ea097e7fad34bfc2a9bbad4421d3c4f1ea4352f12baa18e67e82f74bce60d764608099f8a11c8d63e334482671078f66583410effb00ceeefb59170ebf41eff3194ce84717ae83c6baca9d4d853eff0625f3fe8ad5df517094403b96e6e4920f5257060f0a212ca9a774efb0ab5564a1852c239ac233263fcf10bd8b2c83ab69222347ff70e78f4f2576dcac78ddcc06bfacb120802c612815d99e987d97bc3fd8c293992910b87bd8935ee4e136971d14396a26aa3a07c2547a86395b511f1003845234b4b90568d2201333db964088686e6df7fcb4530e29c3c3e9bb24d7f009188121579839ce239665a85930c1ad9e3bed35d41c0b7f40d0226ade65cffe87bd645d5f46e11e9a4bd9c6aef66f6d2d0cb7ec7c19810fe56f3543db8c6f29f655b8a57dfd0556652152dd554ad1d24f79f3bdd93348cbd8669d604dbd3657c3a229d768d1deb25ef7edde15ae2a4dbb93b8d5fad316273459c9befc742b4a4cb8a221b51bb67516a575064215db504e686441a1f1ba88719dece7931b4545b80c93ed25d651ae8bd0e3eb4140957998135f27699822aa15699086428df1d2594ff8c13a64c4312c1133350f2076bd724b14ffcbb4bfa17e0ba0184590c3b07cb6a0b33ea01591624f064ee9478627ba4a8787d826d85f06213a691f0b821959f3c5cd52322ca4e202909203a5756a0cea3d8dc917859e4783c6a8495bd1e87109fe866500400611ccc642f0991e86d00d5e78a11513313cabe5c178d90563428deb404bcb3b65e167f226ced9288d53dd753d82532f82d03a3c32d2e6c81dd1619b6a3e1b7ccfa6a66af97211e1be24a9c95bc56c4f348adeaf3ea4b784af25218f4712df57121fd7e312157a1b9aaecbfbdd92ace2b839231e77d914b53283817971a00a6da0e6d0b2c0420e5324cc15173ee5f8355081b4fd59db3bdf489fe6162e49c511bda21675738145c8b983e4482423df301bd19ec2a2f6d4197b8ad515a8804e8bb94c116db1119544a73d5b551833d5a412c8c8eab0b749bb6329a1ee8b6acb9ee6a759a4299f54d4d94f1049e090473c7c09d967b42ec455e7faa2f7107420fec90cc596109ee7c040ce2633e6d4a878ba75d75704ee6abe5483eda009df6cbb247b67a204163e8391ca606b5097231a57eb1dd7cb9f1a1086ba13fa7a3fec9cc42ed37d1cf25fddfd7a98e8b50570eb1c83ada4b75cedbd21aec1fd9a8aed53a2fe133350d6fcec562b0ee414a3e5b5a2d9a997db169a3b39fe759bb439f38c8fbf4bea07a8492eead2f39d8a02e105f885563abe73f6184fa28e5f8a83c101d00a5e75cd3f9b55686f0c4174890d2f050ad8e7eacbc653fbfaa52381c7e29be85d2291998199d4a13e74f7f57efa769522d01492ae606a997eca384d0d0bb0d62db5e3ed285e9e11a1c94a1d589456e0ec73d105364d2d67cc2a6d85aa6294ab659c2ccf6155191ec5424a9b6e8ce3ae796151fe805e672438fce84b018204801a5518ae67ada80992379ffc3b46964e7f14e1c2c558463f42fc6e34bb4726945eb29157d1f09e95b3de00808806ca27bee687f389b17c0714d2ee5ac65563797b60c0e8149a72634e75aeafcb1c62daac716e844a83a1d990b9511b0601c85b7f9d8469e62466156ef4bf295b508a6d1c7711254c60f6a51bb38f72292901913ba0b3544cf4884ef42f368fd338f8c1e70ea5455fa203856a221205b69ff20af0ca87778a2f16ca4bc1809d5e42ec1474966204e66bee960fa1dcb7c903489609f250e6d63780905614a381d5efe4ac68b1b8feae78b856884bb04865324706d76da797ff2a2b452b779768c24d55066d64be553f5e948ba11af955af1d89b4a1281c46d71704f7f28ec3d475220aec5708f6c354a0c6a27c0c8fa3d36d329fd096496645070d233cc4a73d395788e569aa8cc857b82e7a5903ba9b43fe0caa153d20ae2e80c96b11f082a1d268c07f3397fb4f126b30e68bcb26c010f540926ed85a7984a5843d79d8c4c31732aa01ba874df778fdd21cacec4187aa816395d4d9ab2c38f162880626db55cfb0fb6fd62934e4759743175594deaae680e8c7000cc3204b0316712d6534b113db47aeb08c4c33ae5c29db42b916cd2bdd627d33f8dca67cb390cfa80a0e6aae8503322c44fd949b5369299e3c2b7baaea149dbf08ce3f8268c3f2c63f02fbea714a15e5efe2484b61384124c2d30c1c99fe328467a6e8b2196737f644af829b6ef1e170e9b44d49809ba8d17ef7f4185027d3dfd627ee873b0bb41dec9149d9635ed6834648c5563466488a935016b13a23f21df1c0129e58fc1984ad951261f057c9d1296d0a23dccc7fe260d87c55b74978e53ebde0f3259a550825245cf9ba89b295bcc6f0319dcd6b244ff020fefa49d5287854655c8e63abe002817694d7f5c61a308b495e46d2ae73ae2d812b0633ff74728f7841f0b1011a1dfc7cd7ef5adafe9b9a496343bb7d423042cb1f50257f2a5b5964680ad5197134b10c8fc0804fcabff15d8ebf2870eec7da007a5b6be1bec3d12f38a5b09ff477140d0b5d132c517a35e7d1899d34d788610ac3c1e733d42a76fc3574cb6c9600e2357c4f3610a1fdfede6c5413bca649d378e71fbcfbffc487ca37db036c929f6e03400d438b1f1280867ab3b2250bc21460bfa65893145e9c74e6cead59238bb254af859951d8bd442437773038d8cf160991dfe97afd6d4fb797a9737200c9d34a9b2609c6b710f25722f19ff28af12640bac93fe4d05e1111445d72eb1b93e5c6a688c95f066979a3289fafa1c59d89c25c4bdcb3bd6c538d0c5295d11c7e8c0cd9adc498ec1fb9ad30522449d5e9af42252a55c43952dd561a68542fb367d234fef6f01f57999fe8fff7c8dd4b552a882a688d8e3e53acbf2d9444df2ec8a6b70a7f52e85f458479afbe4c3746bf2c95cd125fe1406e16956d00c0591f4185cf08db9d23cccca2899dab6bfb40b1f81545bf27c49cfa782e8ba6323761878df1f98f2b80bef95fe6c393371f65cab8f9ff7e2fff4fad466764be759e35d0f7143030b5b118e4816464509ca23c5bf55648464db3f2410203cc16f96c368ad24c3f21572d4c10f69532fc3461d404ee93e8634a703648c04849de2570cea61d0e37d30dec7093a41116761014036b63d3f83c212d05f768a57124960f9df9a7dece8be551ef6178d2a02f41a286b6712ad7e0f9e0b7ba9256346ec0bf7d24398f68e78f8d9f7d4fd2935d146425323bb35b05a1197b2b425fcf2937189bc5f67c09249b380cee0a2b1e1c76bfe8fddda5c48051b3603b5419ef5ef4ee50660a39a0101f18175ae86cbbfa146a74c1de8de64d0c63100efa83975a1338492fb1f77c5a0e268aaf5e9f44b645efdddd6903fe3bfde48a261d2f9723b016f70e5b42467e3ab7266df895ae31c7b330771267ae1037b523fe408e2192a6907fb5fea6efbb098f049b7c7183ff10ff790cc9d2522065b65e279aeb219aa7d38b21eb4efcfc5df805817ab5e9d7b471d6507a2e4f032f19ec0bc316d4685bdc364c1687d587c492cb7086747c9f8f6f85f6272f73548689fbbbdb20cbb3ef9bafe1568b7750566a529f698e4d8661749fd125880c3ed1c0a6f931b43f3dc43ddf822aab6e80b106740d1aa236ad2d1a41d709913f07d6664aa0171d17849ddc0a8b0ea71cc66cae39f3dfcc6b88a36f046b923ec6d1fdfd265c4b448677caba57d3bc5fb2ef9b64d30431ed362343926e0fc3f84793a8eb38f469cc5c934dd81ac296cd740cc84056958a4d85645dd93056b77eb0ba062bd412d67d7b87bd12ec9908b83ad6f0474b12dc2f20ced09360d304c08deeddc94f13639f414b65047c4afa691b95f6b730efd60c0f7726d1750f406a99165209dca46a99531ae2905c218f7df7701b64526e1b5e839f638e07b0cd220039c7808af900566975d2dae3381d1c6c9f3af2b3e605ebe2506bd839b42b25109ac962c1a5637c2dbf26faf722d0253e9cbe248b6ec43cc00858b394a2c9f2bad9c0e7dad6b21a54131ad9ba842ae0f5bf4db2a837fc84510b3eddd02d1567f149494af95ab605290f4c5947990cf0a5dc318c8aa06efd562fd5681d0ae6f08e73255560f06a3354b2a2235c68adebd53f3d91f39a9501a73adaae06225ecd56946681c1ece46224b65bd95ad0b8485dcef550cc634c1809446364dca7b515552c7890a348bb38a32f403e3741474adcdee6269dcd849230a4e32932cef491dbd8572adf9dd507a830c10f5b4de8f05e315eb32511d562306f557a13484b56eb780ce04d0876b9ad6e2afd832179fcfcf06b506c8d8412503371267071d85909de8bb147153061453ec8d7f575c2e9d1512d0bd93136900eddbe59be86107a6c328c6e96b263725108d96d857814cc1a05d58e2c263a3cf159c3f6a7c14d818a2706287c6c855388960a99ba2847572ca6209185fa5126151a588786fc099d5c6358fb900b274f90a1b0232eaf39431dbe07b029a571d7ac175edc9c16b8b9c3c57996450c3072c1d98781bc68feb2165f2251b33f68eab9f4e3bbf5c379aa001ca31fe3961fe03f856948fce8aa2be1f885c508b80fdf509d73b38f39f0b6ddd6079c747e1ed347ef81d15c952d997905aca7545489221195dd376364458d9e89eb198567b31d20ca5136f549428d15149135a36a635c55ec08cf109f3ccf13d5a012f4e780b46bacf1550d041a0ef1f2206cf49c004211404824d62a54a6441931766ceccdf42845bfb0bca5ce7fa97caee1221da9df7e8c7915895e58d60fbba2881bb2a2b66d2742eef504c4eaefd84cc0c34f9da5873e0e1a86fabbd734f7c2d0174c1a255dcc50264616b0771ed4837640b9600220204bc4551e14a2732d627cba0536c54d8c140dab85d0b8d58955186d0599f3dad3697e555ce03bfbb370d73e1713254217c8d0c00a0b81f777079c553104eb9d116cf4047905cc22bcf7f51da3f3a99ea8cb8b9553a62a1b59da438c774f8657bfc11417d2b34098077d70fb65e428a529a8f77a8242608c3df4fa5d83a34f4ef36abc5ed9e2f42fb26af3746793778cd562a65a38fd8980fcdacefae26161feeb656c8640fe8b3a50faaf71a0fe3f4b1fdcf85b44943b623e41fa09310941f8cc21d9d109c75039740c901e834d88cfb9191ea2db4ff701f9205a8242b3ced88b81ade9fa72892dea24e3598c566c7b288dfed7268acf94db899a4c909f833468ed0445ff87baabb1159cea2b1f21030f3a216df134235d9c1c9401ead39653e0c3fb08512730d30741c8758dcb9c0484866c7cf4dfafaebd09e08ff131294bd6ee0fde71d3f40c82966b9ed134ece91c75aa6047e0016eed8268a451d9ef6cf002337b46f1b7fe1bba7f4309c444a03c82ad4948f984af393ab0c64794143bd89095748f8edb8e0ba6f5fd15430617583e87b054db8202719475641cebac41116971656cc2cd282407f63e078ffc87f6f505a8963891e77991bc672d166384f37033d0b9d9268037ee20a1948dcaadf9ed59b1e920a3e13c5d117a21dcc571d70caf4a922fe3e766e072c2b9e21f0b74b04e5e39b22289aadf6c65ac32551423327755b3776b86eaf51a47036a0f4c6da9673252eda3dbc45c5262580e0baf9b1c5385f14945dc24120acfa98c14567634bd33d4d8aaf8bf37f6014624d5bd504ba70a0b2d8ca282af2c367cfd5b48ae431c3ac5c817ac3674022db9a66b0353588048f257736d37d4310eeddf4b3109915c32d4d049999a4ab290e9e574aa4bd86d55e86eb6cb230ac6da9548219de19a4c59876b1b3cb57e7c43a7db25164d6f989cdf1bc6f6790da69b3579a07e95163e186a0c0406191f4b723f970aa7449073a814879d3c2107a393f4b8d3a5590bf7c53eef20d2f887ebc475d48422d2d1d7c4a0b1602edfc2cbfb67e269c805844f5f674d4b61209f810f24aa74b08a8b95e7baef2369348a25e62e3595bde3ba2e2c42fecb765144b75c9737b411dc7c97853711efc48479074c3ac5ec8614d99580b4245494c74f67c38a90b6a0c28a9cd288a57ba18f42113a33bd75661add9a63bef68d9017a27fa7bf33b500413af0032ad1df94346f014303749d1c2bc18d830399cfcadc39ba6cb7be439f034ebd4e70f96d1e1abbd91554c4b5d0e7a27f897b66f914edd89b9712f4a73fa0f8095e4c5f9bbeb8cf1d38bd6b40c143bcd850cb8b59a07498b162fb5192bf2f7cdd7541c43cbf1fdb85f78986df00790380fa8797184c09c5e47e510fe72d4a691ac220e517def0d132947c4e3fcef90cc6ff6827a5d7f9503712409da406589756cb5403eb042b5514fcaa1c26378dc5578b55d5ea39bcf881e6d072f70bf37313142c26b75576ae333b1396be2b4ab08889cfd8bb73a6dab57700821d132ebb032c59a6794ce14da889d5d34c991e1138acd9ceaca2866b2b33c6526ab6b86323367e314702538b939cff0a753643c999cd5276601aaea3d40198d0172f6c7105e4355ca92b8059289457a0ec639009cd4a3d2b6e72d23e73e76b3c30908cc95720dc109dea83777956d1325d3f3ee63bd1db5e23883c5908025d1e2428b2641608a30c88415753be784777f235344084e83ad08bbb9a899e7b3d66b73b6ae7a210e86f1886a88ce6c4d6067e9d65ec4e756bfa94e8793efab25761560a5974ca6698600b1f8759dcef24478608a620122b899f26fcbb8a4269404b30855fec8b0598ddb314342428895003aa2cbd07816bfc001ff64e6b69f77d596346dee52773d58a1eef5062e8023657fa09a6064ee291364d1d3b81fb35065788509128422ff1fa9304605a059d30810415740abdee4e720fd4caea53328e97ca1928414e6afc954b079bf2498d6d0b9e65952aba76b2f18097f70e0b3cb83aaa0c8e8e7c963c49c498378a7c7a361cc71012355924bf7208514611cb8e9f836be8794f0dcd1b2dab37798d596a79ce9fe531ff6cfae4a552c7a5cbd51730231d63c6810a5f5b531ebea804aa128c770d6c5461247564b9988ce88f34d55b99b709000e3470be8733d4ffcb56091f220c451cc5fe8e275614e98618c2be276ece15895256f3a30a52f4080e35c2b0ed33b392a9825eb46608119b0ca0c250720d4950860b44b6fc70382a5824017e9ff1a7691316d405929cfb7c1967fd9061370a185ed8b7d9a04c958ceb752d863c3b7551c718df2a341ee13d0209e77de0d5f07acc830ca80b488244f19b58e9e6e489febdcfcfc32df44ccc1d9db57763f9bd4c4fce2a58a7f82de8a172224a8ba99b2c786749bdb06cfd19a49917a7bda14178ad2e70353378a289b9607471dd334b0a482b162d0cc3e64c64ec750ee952c6850d34968cc6e372ca0e2c12019dddbb05a42ea3865581122ee032482dd1b0bc562a2dd8981af201ec970e95dc4f08d7d4d6f49dbc1660c6e1705b621a8821fccb38391e90243946115b8d932b5f041483c4a8bf71250480a78488e4564cde6cce93c6c3bb9b3573885333c1c0253048d3dd73aefc3a5a0d7b5675b5f10bc74760a7d2a97b42202cedd29cd06003fc1e11957d5c7e46c165e8ac3338f610fefc82533c807c272fa32ae98ef26a2ddbc2889eb8532b38106c9e0243948e2ea28cbcd8b8f914db1ab34466b38db065130e64e5050dc45117cb4b7e1c3040a8e0e1f220a71597bd812de11c8116e06653fe98877806accc10f71585bf36c315cc9c2b7f33cfdc9c3ef8619329419082d206d34c79c4b7dc0e39910fcc31066c6e7ae376e71cc35aeba410cb22aaf12d2dca69b78af4c83da63e671d9507e89d35c747fb912cb285345a16a0b342d50d347c2c914079432323be310f373b33f9d1361563683f9b0500119543fd5c3307c0314733a46611fb6a256cb981a69fd3856cce57ed5328d07d5a1008e0ade13ebc9a64f08bd8f6aeb08b711e9c461a3a178ffbbbbee039e069c99ba20c5836fb1c7012422b6f2d6b74405fc0bc6709a42bc13e86ba8a20dbe3713f52eb014ca701bbeef4a0b891d0ddd2d7c1af20b7a9aa7e1f3acf4e2fe481a1691aae25b3b6e583e94b60d3a27fd79dd76a645e25302f87c40c916239db8b4716d7c2ffde5dc711ea13612feed637d31eabefc7fc163f1f9bcc285498eff66835e36af518e8d180215ecedb76d8fa0f11a67013735c3bdf6aceae9aec3105e99df212354d82a557d12cdcec248fb3db5ee88fcf2eb2eb1dc185ae8d264610f09a50e341db7f7ab7317ab0bae69870e23afefcb6872ab78006241bf10e3646bdda9a08d9592e891ba77629c8557cc64a9c185723da2d0f6d115b9f35440f589543c5a1c9a0a3689d42aa3dd551a48ec3bd06fe0dcbce18e15f8f82864ae9079e833ebb4547d57ef61e4e84aed93840be9d38b4ae8be687c1bce5c8671b3b0e889c640621f7e432236f4c689ca64c2d39a7254f45b030991bd623f054a5886dd470177540bed13af51aac6785bf18cf687f310d13bbecdb59d2e14f528c7891746e1c3036c7bdfcac68c16e1b8dc8a91f20dc85f4111b451849db18f0f10b38c184e69fb64afea8dffcec79f26b91701984336b4b031328298ac14096b59f53aa5357033eb9263066c91b5d98df69897c9d6ccf9fea5e2ed702efe902753389839c06a9f65791a3c41107ed5be1b30a48f48d3961fc1948641c0668a35e7a003e59860752c4cd9058518339e198c3ac40402871d3c440c40e92c8d0bb935f407b118ed400b80c2008d12884ab40c5f6a3a2d5c866a3cf35bd61988e25491865308c61d21ba28fe4fd32cbb1c9aa062fefd97b166a4685b749e61c13b8c1e82195299b56d1b442201cf5d7ef3f915cc20a7f1de1d140469ab273a1c795f4489b89865693f0f2691494eeeb91de93069b6979c71e40e430063056c373f9ef1f55ec10d1a696ba168e9ee28dade51ad9f5ab5f8fd18ff36fcc0684c4256fddf9bf1e1d7551c1fdad26fda83a750bd2e370cc566a3b775635d7f049989954b171b03f48ab5b1e33f2f1552165bb790d146458c99d54ce38c65f18723abef30558c0d063ca7aad694460d628059063b16315492a9231095b94be6ecf348bbf948e47809f6fc2583a382374139650653da3598369522028511f890411e229ad5f3ee6a1292f22a08dfbed0568ca04267c0228904f3410a47cb1b1368a55fb296a7fd8ab2c49f14ae4a45450eefce5326c5a1630e6e14f179c3f4928acfbea5fc7f8a803ea264ac5d67fcdc176dee0f0d63f2d330d0664b11a74311488d83d552d1777f23b604acb3dc8adf54e4a9b49be72f572df4011e9e63edbb7da60022f291104a009e4cded9b362a16dc0b9d8bb72c447349b6c82d56285dc961c524757dcd4821d136b2ccef38f09b994d88d4e2ba9ec23b3f4a24c53e016c5deb0dabb305597c1342a701f8db137acd393eaab07cec87fa80b251d78b18dbaf904aeac4afec8ca8a2c15302ff9a1e83bcfcd9e5ede83586e3dd05cadd78ac4c3f5e2fce8b20e9ce9546c19d0c51893989895000e56dfb03ef37556966829be310b0ecae3ba4e25c55ce8c04ae0eb843b9c1484fa0c0b9226a2710fbf7a5fb9ae4bc20755b2890973603bcfc629037d35c7776f6a28940cacb2f96bc1a9df949b094a90d7c9fedc4e28c6fbb4498c552975b6f58b63e2b3bbdfeb9841170823a8899ce7f23560f81b8aa64f70f429f0e4f3822f324d8623de9b27d4969a74d55ec643139143a0ead3829e5da5525ceb24fb6f498aefaa0f75901b5f1d20e6aa877847600d04057321d1aa74071d231b731f6b4f8325ffb37e14be380172e0bcae1bc35e4a2dc22aab569017ecb3098381f7f16f636b6e6a5b59e85fefbd648ea8a2f5e501a95295320319acdabb13fd6648da3490d745bcf93e00561c950ce6866ff8eb015e71551021feabef0429dc4b2003758de2a88257622a6aa73381aa16d6ff35d63edc9e1e57a62a059ab69e767ed0e62bb6475338d5876c4c6d52e6d307088f1cfc9ebca9b942f987ab8fc06fa7ca68e34bb2d714402998f7fbc39273bc25a17550debddb338970e59e36543a4adc3ea2be41fceb5b46681a72ebe486186fb60f501ece2f655662647064845a6dcc9bfd3d372f0c40f8823e42464c7d5e2d60cb945461013bb5ea04bb23f797e82c617a5d4d2223fe7e6f8f09d82eb45368c2ce18a7021f8a0f7cc91899e8d8105c9cee3f83426c3b2002cd293931651d9c9157428089c847cd52ca6b69352e29ac6b010ba3e78708be0ae5bf7ad0d974f0f572d72a0983498767a65fc4b24a299bb0ec4b4d5a15095f58d0119d20ece2fc17a63cef07d237152ded77a788d0b1f271035b88822a18ae72a7f2d389ca2eeb12c1b3d459856c6b23226a79a0a47c338448e37889b3cfd701124d33bcae87006299db4b1b9a5a510f8d309819aea31ec24a5460fc6d8a64f62ccff5767d9cec1cbc8932ea55f511cf668dc1c5d43734c701b416ef1d774ff35cbc3ab88f8c3d9601393cdaa071b61d131651175c342c3d82e6a8aeb598ed80cccf61540cf9f38ca342aa0cd401d8186af6fd71142ae91d2fbcf1081f961628781b96217f4f5135c09f6f5b38c99bf94d711e9fead64ed2d1f09fea19d75bc170f323a9d3022b1a34a82b71714a6418462a6f08c22a38d8535c3bc6230fa15781eba2c926ec6faeaaa2b2fd6052742ecb5c14c09941c8f9bdb362917c85bb1c43e3135996361063184639224027851dc500c461ee6d490580350f9c47be189cf4fc8207c5a88a1b9387bed77b4b442cbb23585989196bb75d91e13b66e2490223fc5598b356ceb2ee703ba5e61cc1153fc637b6c7340acce40220bf6afa51d05daff981ee3667b03cbe633256bbdfb1821247d356c276f32fcfdf7a7ad0aeccd444b63c2cad2edb35bb10e1f1bb727fc9bb0ee0aff55b1034b68d2ac88a08c8207804043b1018ec785df47a51c10f8fcd9b59a68295e0216a32b6a6ba9e07b17a0ff081c433ea8a9b8d113ac3ed8f30ffe38a96ea068ef3fa850c405c98a14d4126fcba1715e051da43df2f58b93771e4933f12b71421736773a1cea5b9e5428f2cbe94a89b697588d34633310406617f217293a52b45eea1dfd20063d102b69ec69dbe3ae510d1087556f043d18873fe5703e944012ef206afe6972febe612cb780cdeb03333985d29101b08a40891ef4705af5024de5943618cc1c38a492f5fa5bc42098c877a650be4acb6c68a53236fe729201209398f338d6ff0c4743e590dc80ec734101e64296d65bec59f216ce2072d681de7a4b008b1ede67be3edfb450c36bdca9aa13bd80b03579c463327e7bc87dc7a227f942be91f042e21ef6108d00fb1e1a325803fb07ad9a7c8d76a7922068b5736a7f64af448ddc93aa779963d7b138d6082b8bc641311b0fa02a46becbda8832924f08019988d423a30e0297ca9da1c5da4048e1a09e8dfb74fad3824b9413a3a2ed794042311074c81ea6e80230a57fb2afeff0eceac7f17b8a9581304b412634d1c448866e3acd0c8044f4767cbbbcc51bd65edf29832ea0d858c11cdd0a625de38b430a6e084eedfe7b187c15fd0a0005c455a887771aaeb7688eb731f32ae65f8e13f12540c8beac9232c8de322f2bb22ee19e356fa241d228bfed7d54d41887e1ebbaa697232e2695c2dd9e6ea631a7102d2a562148322c4af66c03e10ecc0c5df709c014ec07677ace687612b867775678bc0ac2dfbc3ddcd722637838b49af2a2184bfba7221af3db33fd015815638d6bc19fd6a8beb146a3a5654696aea76448e89cc06c4dd677866ab9dd4b568e4c41e81bf695ab4d40b1422906442e10ff2c6e1e10850f6c41a1ad087202065388ebcecb2add9c48332dcfd7c248f335190630e6b967ac6ab4b36c8a6a810032c42fed9f1c8fac16984377f3f294eeeb9b2895743aca2e7defe6dddee9fbf1fb883bbb6809d9af0c61617790f36a2172f7a2244416df10147ea817ad838f0591e37ed4d03891991d856f656491be096c1933e37ee8432ae1be3600c40d68b3bb290ad576db4587aa10e4fbbbc420874e2c12625d0689d0681998442d2d9be4232965f77bf0b58cda5ae8286e87b8f2d885437eefae21899257c688d3e0de7c4711efd832657460984a0c11c65f69046188fcfceeaf1e59b0f832779667f687df9eefe457a89cbe915db14510945f50f7e365d3ccf0c23aef2bc676dffe22c1868d30f37ca963402fd6b483b4edfd3424267c1d3976f5a0db4316112b1218223ab75d8c013c84b52a8782471e0f583e8cafa7687f661ad1b4369985d5f5f837672a45e0d8111e959fc0c31377e06507faff0a01cea18a968561f4a51e996bd3ab3a68c77c630c31a1014776463db8df50317ffb2dcce5ff2150c46920281022b7366e44471ff8fecdc1189ae0ab9aed2ba9d822cf9ac2b927b50f66f48aade429e0621afc8044c1afc061e23a60df3f80b5b1afcac28a4a686dbceee0a89b4d550178e4ece51537a5c812670efbcdf50d1468520a765fc9c7f3d45010617b6b5999e48d2ace2675c1f819084bf17fc8af6e2e42ae2a645f225f068cb00644e07a7263297bb4a28be317194a0fdce1204ecf7085137ebca053cb10a2c1c2b3716519ac2130a6a84f891edaa2ef17a83201df48be717240e6756d8e1950c1b86ce0b4d911dd36305184fe64345258267e70495704210d4d441449279a18b313420b5d1430d2893028970f2592fceaf48e28d2c96653275a79e644a1c5bef802590a4d691d02c75754e29e5cdb6ffcf2b20ef0bf372769fb56e38ddb974280fa99703d07265300630c4e01eb07299b97c1e60d7d17ff2e7033b45b7ce2bdc00fed950444e15a213507b4b835b562cc43ee3e742fcdc2266015e330873605d06e01c7464d909dfba424c60621c734620b283371249b5775ef45c1d731f95f8288c44939968618488168e9016c7329ae6ef4f1208fa480a6c6030872ffbf40e2ec196088adf6608473576b533788fba0a2e7531d5aaa0723d3ecc4f8261879f5aa31a3b60a8a824c6cc95a0106c7fe1948b20243126db5004140f611e8f4ac762b6784cb1789fef7a714fc1ac4dd86a645a63b5d83b69960c99be4c3299bc064d6e41eaa0641bb05b2df9f5c2bfc3a30d9cb2a81b74085ea37d395e6346ca14b4b6d09797aeab96cb4997c8e63681fdc31a978f20aaeb084f93c524403ebf32b860a3d9634c747110ba0be2a93e86e38f73bafe6f97eaf61c4e72d20879d829071e2c68cf478319645b20d1cd48990ebbe7ed6cc4fb6c86cac19c99643273d8226dbe756b78b4e47e1d050fba986991a89c0baded0d2d4722ccd51f0b2b384056ac0ebebdcd4e26d4e6c99b9f1a3d3f03a0873d5ff3c7a43fdbdb57ac3ff85761e8312cb8e81263672d633a495840ef76d7914c68f5481c93a202d75afee48fd98cd176036bf423b83824e5177c164690c706d55b3e95ce69e8fc942f477fef7fa83de7f88956b1a84aa706ac272af7999dceddc3c2f5443b6c7780fda4b8ee00f576590b8add6b4e841caea576b80a588fd6d4ad99e252c09e07c8c5d5e1363b2439d19edf337011cc38bb620cf49ebc1a6a8c22f0811513e655c048792dc64f21e3692574eff4006037b5cf5bd0bd8317d15aa884d0596fbe295c0336d138ad63903c3c3e6cf7876e701cd911d0641d203dde38950032c62a64d12fb52b999ec62ddf3f2618ebb1b701d48b7c741834a876b7c3a6a986246bb5a7f01a7952dca7b0e2c35b50b0e551f44f309b4225e98eda0aea7649b79c1d4dec675212d8f62be35d6eba0184709e6330bda2634efee5715611bd0ee2ccf86a6b8b226323df6a668ffb22f5068cac531376463f5020a158d983c4822c859d9457cba005b94b3eb12852acb801ed91e658880cd20f4ddb4347c146c280cb54ea5688f0e0b71019b83661e1bcaf53a7700b9048582f20ef8ad93a0d579ce691f78530964c51d7cf1d61038597b62b55ab0fe5c051223db485f250a970f3600c384418f8f4c9cb5d196d801191b8ada728182e42685e253d5f26ce0458fb757a9cb4bf3b4f71ee03cd3c81c4f2a88e1afa053f13d4394aeeca31dcfbc2dca14acbadcd2f2dc551413a221bffd715881df9dd0b1234ae919af16ff1ae18e0c3fad36f9670c85b5130fa86b70142054499d177d4dee0eb506453b319921f2955ee4c99e8006f338b0d6f4049a13c04e7f2442da35c11fb54ccabe779c1d6863cda51152b70a8950b3015f421a6ea7d270dd2c7a1e565efc3f4c122e18ead44e4dee531e094b049bf75a8a97b3ac76af4635cdca89270fcdf6b0a1e2c9c3b73444347e9f0c2f64518ab5b957deaf0f1663df2de333b8162a9a215a75c12dc8c511c93e8af972ef9821004e8639984f3eb685aa762028ebee7258212d198c0812e31ae113becfb43de7996d992b973d1c7488518b6d30e7f804cbb3d1984aa18f1d31ae4c09f1241bfd36f0446dba22d8f2ff7619a0946f01a88edb2af3e0ec5f0841f4f905775b97255c3975aba928af4347a0fdb426130cbd3a8be545b72bcfd4e830f78a45d8578da8347ee2c6fb3d89a0bf4526c74138f32f0b02a72d8326684a4ec003f7d80080cc7888d3c1bbbd46d6031f9b343d8b3f4ae5624db7373e38bf2c545fb7578c4b284d7f6793aa76f538b907745f85eea150c787957e2dac823d0d315bdf59aef71c07cc2a6ac0e64c5934e4a00f833cddef4512991a762b833ea3dc090b714a889c298e993694a388ad55fb833b315b112d985e9b77618fbcd0bba17cf8dcd0f705f762485b100a7067b174580a3eff1dd56791889f632c733d90846cf8781b2c00540634257e0b9498945a704ae0d7ee0c1474282c59f9c1d8cddb97fd6091de26e48e89dcca34e75019a2cd82766998a46cd5056217d2f1ec14c1f1a66d35d7cc3653f6aa4bb957f43b2d28cc4cd32187604c9f295aa04fd038a302b5aacabf079424dc698c0fea55919bd93cfd860eb0207baa0eda1531fadfe0bcd38845342bc69035829ecdbc61733e26e941c844680baea6cedf0953b795d55e57d16c006de2b6a55e13febe70304c118c48afd83a77c36f578e83bd90b376e9c109b8fe2634d69333f5b7dcf74a5b68bedd9284131d3c1fef82fe8cf505fb86ee1d008f0a0b9b7352e1f1c28448e64d671f54930fe805cb52367326344d846ba474d332a74f5d36596e1470dfc4078c1bc042d816f48d234b2d29ef02d4efcd4b68a07359bfecb58443d1c5d3312f8b7cb093057f813eaf251cec4145dc2aa65d6ee17229d4d839cf897eef40eb6b85b8970cb9f80242839635a57d10bffefd8b72211a3e14120c7913feba9e0a8d157bdff538ab9c6b17b4c0e15a8e9b61b84f0c5a7cd87b4047219aa5e5f2420d838f2de7275b934293d36eed7b30c6ad5e8c2f2b8cfda3eeb59b3685787b761e7e6b538b5f6b34fa179c1b2ee6bb4166736951fb6a269945c782f37b83353ee859c2ba2c7d01d1b6e61ad001beb1016b04f4cffcaedb06f9986dde62266fc5fd3cb12584b17c9dcda7046facda4a21ead7a7b37d4477fb49169aa24aa0931992974bdfbf1cc1537b1ef439d2e0a763bd1c91a2141f8b5e8e6f0d1c89d4f346ff93ee1564342b430ea68e3254ed312bed718fe263a2d01569c977102f7f408923b0427e5d8052191b022346974d9fda0169aa1c7a75d0cf8d8fc97aa1c4953ab52adfc286f7015b82633abb05c9368367493ec1aa5fe0e00e8e5f067c1691602ebcb1e32a4b003af59aeb05e9fa5ec905c50eea23185c493dee81240940b480bbf7d8b836b0f52de9c9c241111168b05ebd061968d22e5d335323e3406ad14e990182b772a9e97ae72a8d6db19a4aa9d098fda5d277f0cd935e3e025b9c07c90bde7176e987a0463ffa405ceb1f083a9b2bb773f33d6cfa6fe3d2696cfe88cf7668f9c5afa71df1bb75bf35f8b59b116bd2e60f1a1fc3116ade6ec7b8b3ebf636b2d1fa1acd26ecfea01b4cccaee6ed0093dff1f2b7dbb241a5d8f45c4c63393eae72eabeb3ca33fbabb05679cb5c7fadc06a0ac577e79b26edd3cd1f8e808e36f087ded9dbbfbb2517621f5c179278d989c1709c15baf5051e890f0d88f24523f9ee98c676cb289316d05a211c51f37628c4eff4f9bb4b8d1dbb914cd8466b5334028dd9cf14bd039072a2dd91deda4c7e7a4c34fd7c59a364a27a880f74c92c8af51576f27002c621c07de88aeeb015f28af2e0335caacff5d90156791be9834befc82543485c731529e75b7f15a39cd28e9703aceb142aa2064d701bb0b2f71d9c97182f8d5c0b4a9f96eda611807005f18260654470c6034d3d6d0a263406d132557732bbb02526c9d1ef6a3a8efe249a6ada2ae69e2988ef46d553a573b17994d2b9fa3c4eb466ac4e19d2efb6b68f7f648dde834d2557740b889b1bfc2cbde94249e6c3ba552d06155ed24be09c20a310e89e7a8999d7f4313e2b001fc351dad9cc81000959289762bc76750bb88c6357c7e1815a3eed994d564fff6b2b3aa00b91ac69d1b0ab4592de720c9db3d5f0a6fb818c350fdaaf3c509fc428f4961920aa474aaeafe90ec2d1ab850019e83ddc14d0961b5742c6a0e93c93d197ccbdba42474b8b17a5a7ba5b465d311f013ccfade23d07f4d6db33bdfa007a7186ab8f9075a127c5533618d1286dbc4391a9b2e2eaeac9a1bc41f26c4ac6c25bd6ffacb24816431836d51462675b5323e4d900dbcf144248336571b5a519702aa54c55fdf827b314516019cae05446084b2c1a89e1b9fa02687ed7f3710ad5af3aa89826c09430eb6199ade3a7fc9a64a8daf07bf1f259c7e3ff52c6373d6806bad5446b9229e94da4f66f5547aac7b20295a1e0fde63b71453c065251461423fb6920c1c4b22b2c4526ec1dc743886791e7c35826d3d871821332757e70e265f685fdd8c259de0556b01d4834d1d365126d765e6e0d3842551f81731d139a81725eed6f5367680e73adf134d29401c0d97a729cd85cd7c8c4763a12b63a3714ccb5b4c3740d3469725c97f25147d0ef6e75e3d7b4568b55519a2a94092edfedb4e292d9ee74491eeb24057ac8f9b10fb18adba71410ec7af4d0e71016d118f6c9468f737d8a447d70d89151c460645561b40394d605b74573179d27a4270828a2f0b4335345f5e0de95718739999127b2f59140d115250a90196aa2e656160225262a6e69a5a0c8a4e0aaac128a9a14aec2aaa0c809c55584d59904ec23e870e341f70193c53704da9520ae9ed547dbf724ffa29cd4478abb76a4bee9b0344c98069940a47ab8f61c6c3f14b6d9211910dcc3321919301497f778aa92962f9c62e3a2617abaf904a0a4b92ec9271859971e335a4831134a5a897ba0b49027ebed285314292bd72ebcfc838b7dee86449bfe18af38c3b645d0e8b3a09237974f1ee83551c9b21b4ad684c98a0aedc6150f3c96c0c45e429f1e6810d10c8cb92d0adcd2818f229fdb54457fdb3bba0ce4c28381bebe2f4e95ba236d43a65d496e3e5036262ef8af27556a9ba87b10c1ec14059c1ddd655a9306883a3ef765b86cc7194f6b9f8ac4eceb91c742858cd06dc0927300a018797d90bd2488458341bfae8974470c330731abb7f19ea41221d18b67a0e308113380c2103ad32b4c423f3e45da9a70cc331b47a6528ed18fdf70a235fb8fc611c2036fd5c1478cf136e459541b583f3dbad5fe24e57464179ce34dcf59e5b7b8e19038e805b70323de9441f126c3fb735f93cf0926a8e030af2c39c1850df11e727e3c99e17404d6e2fcbb7586b029d514b93163a6eb9333029545d96bd0bf0e776ad80ca3211ea0e5dff86f1469126667043d215a751fdfbbc007ccc4951137ef0815527184b099205a634400a567c02b641850d1a74c1ef26b5ec5b5cce2d6761351a9e2dac4301f45faf654e0c69c00161d674564eab2b8f1b852780f1d13b2b077e4d8634276c936898e9309a103fbc0de02ce68079453281a138027a925ae0db64ec2045ec28b92d43a241e96eca770f78845fcadc035a5cfb5daac514aa48c81b45ceb2c71a44812a93f1a2e6fade9ad417c414b045d2d772190b738ed033465f78d969d979f9e64aaad59dec6215ea1e50c1540dfedd3aff73f2f508bbdb7ae1d811a4ff7c27d7f8fe947e46bc564042548c3775c0c75077e833731921a9fcc5e34508488a7c9e2c6e6bce91ace3c3f2794f5930bab94b2fe3444a2cc46f15f4d4213e78ac06f8103063595d291033543c296ea85cd02762854f53cd3ee090336861a9caba32e1276c6bad34b47ba20983a23651918e4f879dcb234b0dd48b75c2c83e1d8c704b76af81caaae88397cabacb0a9f2e9945d16ea7ed3275ca69bcb2fbc63354388816e1f39bf518b91aa8c0bfd536c5aab6fae4237a8360e50d7a7d521ac02c4ccdb742594aebd3126bf088a21490559cd8042e4d064b005e0275388273075349f9fd4ebc7e07323761094c363c660bff6c196cc81b61ba0dbce42db7cc3be78385bcec67d587bea8c82f8b80c46e7c3eba0a6ef17a4794d599aea1f27637de12ea384d50c4d81db9be759b5da551a014d84398a686f0a8d342544596ffc0193c23e11c1635a249c0d51e8686606f7a690199221ae2d5bc4d09882832ec13925f85e087a944a59511bc55d47c9d9e9ce193a62b5e323d2259eb0bc216eb2ecc55f4ff8be7306cf1d010addf6c9663d0c9ba50362494141cce4ad093146af1388ffef4e202a5a9d526e005ddac2346e2186f74ed0a735956293968f26fb96637d39a1a6111dc0be4f5c269950ea9a48b517054282f0fb9fa511c9f0a7f68a5ec000eb891d9c6aa34036f9f956d938b770ee9da52c0c354467993e8bc8f2a36c4fc0cdb713eacc7e2ebe5dc89675e0f28c03a7804e02cc6b14b332d438fb84b3d905ccc339ad0be0227b49da8ffcdc82db6529140bac6db6cc72c3f88c557fe1e1e38a4c2160428b16c7d9fe3b02f1937d0467d9b3a9e4a274195e1a42cf30297abcea7c31bfc0a9dd00810ea892229ec0a1a536aef95b8bdaad28081fe36ef58468b5a947991d7379c9b02702613e05a614f5b2963a0ba25f62af17fa973488da43974f9458c524dde8b847a14779653d1f8a74cd6dce0d938ecf30055ca210b47f1d5bddcadee206a82ce5852343767ac014aaa17f992494debe25a1e7cefaad09aa6c0d71c1663bf78102631cb01720501909d099d8c61f69888ee82f521efd015ba2f4338f05d79d694790533657fe62df82b76c5e3f34555f9b2f0388959eed41dbee63cde7829eed34938b7cea7e5ea05721dff8736fe868d0eaa79eb20ec7cb0c40d6a0d8dc4d3564675852570e119ac7d5c3e6263f4b6634850c63e241af1c1a095c0d4fb66ebc9074091c20aa1e71a78b962d0045581559738cd80f035c03a7b7f04a4f7e2566faebae7d8d05ab4fab526033d6e7b680eb06952c52fbdb5ea0efee01b94e9ef0aaec20b5cc383e79851af3eef1384219122d5f64992c7e0688242bd339ee2634302f8372d24e07cac434d5048972f91b5edd8cf1ebc8bf592b849b28f2cb28cc53e7c58c28034934eb3209a557c65f877c519c61c0e1f846a91f5bc59354facba84c538b4644891fdfd7f201690bc408524f42a18cdeef077454b5666c9f73db1947d315b6704cbc03749b869f38cbfd172f23508722bb45022e4d2d19e3473b78ce8d8e7c76f6bf2469b91bfedf7d434366dec9cf446cd7290a477a6051049b9b3a2d46ca6ce6106f380c42aaac2546dee4220de8025969ee4127e03e33db4c750ad03c4eb47d0d09d00b3c291b78f5ba8c43f22aeaede4c608809258d8404834e39fb0b0a65325a7bc7952642a0832599078e493ec2cc06660aa3b209ebf3e6be44735f01486b19ecd421c99c9db0c8bc9326fe912751b93659e966df19f2f612ae60c45b30ae292977d367f113de79923af9c830616cc85182911151cee510f6cfc829299aa30d54d4e1dd864e5365506f34674402bd2ced601227389e688ba5956fafa4c23a2afb174429f836650d6257d8da882b62547ce502adbec9dd3ec2b57b4d1deff2705d250ba7151c148794a88d3d622ac66735dbe9854f69834ab15353ec0a964ed807ca344d36f3a653022dcbbae47f6bfffbb138295dcf295d0bfd48c4ddfab0cfb26503411ee88df07a211104ea46c096ab10c75fb7f92b716206bd604e2da40007b826934753d087f6cd1437305f6a4f3242c7c1037e18c9dbb18f8a986807b1201008519b2e928f23eef5cdbc136241ffb90e3dc1c3861d0b85d0c39d954c91be06dd811ba8a729ca33dd2442d380fc20493822d271e08b1d6078b58f284e13737f4d309234d72e6d7fa95e6e1710102744c75ec0f461c2a95d3a0a5901c1dc2b1c0b77456ec957fb1eb6cceec0de6af21f41200a60f96b564df7d8d866800e91a6710762ffc503c0a8563e419f370470806691e3425877195eeffd460f8616a6db49f344119e9d42cf51066baf2ca66276026e7ffe5e12675e4997a4cd8c7e8cd651c42c93171459053dae1c018fe5ead70ad33bab5b8de79ef610539b72f40c3aa1a36ecc4e6c5eff02706d6b8591b397efff9356660bd4807e7e550e5a3f01fcd1ceb045baf9f801753857c184b8e148f455d5249e83624674dd200a9ba1a2fb75687180b664884a179b4789ccbfe706823eb946f52923df072291c3b3b2401589f1debed268add0dc325285ede61d13c563bac7f436152f1cbd22890f82c8a46e990b3e2d3f83d211cf51e5ef9840058d93e6a3e7c326c1600d1611e37c01c6c371cc1c29144e2417cc74470d8e30e17054339ba0bc471016fca136828e8bb4b08566c25f9fee85345986f082ebdf5a09c34fc9d75f56974e207b1b515d4d6b18d1f1e80c006181508762035a080cc99761c841efae19dd4d64da264d051270ac7fd81dd99859b649b2372dd2f690402c73a9a963d028c63c9284d99c12c9da83042ee40dd5a34d742ee56be3c194041f1470040c51d395fd8c175dcd99b2ce28edde0c7e0d70da0bb2e5ee4a050b578eaa81dbf6dc41ae0b5b2e746ac183cfef5321ae295c5b6a779341a0959b2f83f289269c5b53f1ad7f9894e3be87400e0caac44495b05d3dbda2f9d34f5db7e5dd1d8f9b8aa1bb6d2b266f96fa9ecf798c2ed357ae93a542fe034f72aae4aa91ca5e3f62935ed202ce95416706f5edc7a0f105263c9d2a3391307bf922dcc8b976a404d5076bd9d2c204bf3e91e4be997f0bab2612900243432e61b61215dc69aaaa13b8e33809448830ffb59564086c2d064a53be483cd4b28cdf8505d1a6417342641cb243c6873c251cdd9c1c5a2ea9115ea133815435248a15459c6d145671d27988e5ec0fddcea6cb52fe9fe3c8a8dfcf85538e37fcea54564b72bda0a8a42ff3c42e1b7ed2091c8d1aef7085d92981b0fa4222b46f79a30e0c3cd688746dd05c2c4c72066be4f5450fc0dfc4465a57c8b315d43534d22cca73c6761ff9bba1ee8774bb6d76c2e682c101a538c93e774f73a84a8a7a85dea60e57ff3443121f3317198a4d5a4365f674d2632a4f0540294ef755e6b6818593337858ab791bd8b6e54ab6db0c4792f4aec1a247db3c0899e631690f53fb097a910187f7ed716b162e46c5d173de79a653e0b06ba9bdadfa43a224b7062b78fa6b4f3842002c0c9df9d8300c9b6b54f21a220f963312bd44cb0a3bfef9e79d21e3de39e9fb34653b01e7da5a8561303f826192fc9d6b0b46645cc376241c965282484a9f3ce99ab394ef3cc0980a67711099da8d9474bd8e21adba8a976cd2f06c738c5c6176f77b34b3cf6a73d0ef2fff82997fc88fdbd96f72a952a1f3e4dbf1e464763952d407a198ebf0501184cc938b8465a5069562069869830b198f5786fdab6868085b370b817e2a217572ac859e83f497b84ea0a0128e313a49f2f868fb8d9f3079f6412b81eef22db75a6afffdc6f6f70059f6c07ce988318633c84919a600b725a67f324816c5788ff5ec524c4428fea728b45f5a7802f99b2dda7a250c5c44b81c6053e8d7d8193cb8495b419e70835676f716ac7bb407649242b925661e43a289aba3e441ec9c5bad564c899e6245291603a1808b4c20ed52682fa9c71ab164b46c14c31c687d9a4ce9e3dfb0f054b906f11b135953815ba488134207e5f03781f778874ca5d2dc24add28b70aaabde8203a22cbee7c14d14fc21e5dc529d129803d0affca1931d01c83dca398b2e8a010cc84a98c260c3ad750d18f7f5e8baabf579fa39184b040458921610be7498403afb0a575c59805a8fc53c3006d2ab15e78718b6d5fc876c55b6752cfc6418b08893daa92e0ecc55d76feb897a4a20fa80c9a0be8327f82295d87704028c26e04f58fceb999f8149b886c0dbc060e7600410a5dec8f1bfad7be3a628de3e8fe782dfb9bd10fe898e4adeb3155dfae1359abff85f06d70001d9403076c80f1210b481c68b1c21593668bb91b494a5928c277c47ce152bdc88cb25930033c203cc7e971ccc35c52767de00d87455cc10c507aa8b2bc2f4a10ba0c741b516923ee6ef19df9f27d7beb81a03cc285562a0aaa17b35e2838a3eae64f531903823600c3edd8abac92a2cbf9e2cfc8612e5f91b35a5a61029cc1761c5c78497c7428531c4d02f271e0d040f6a4d13659135a75009f05b0073d6daab224f7c1cb17c4a004ddcced62a9d9920892158a07784c9e48ac9fca26c22143d827a38d0b918361ef3b13cf9e4d113e836e61e8c8a1ea2234b7998a06f1b8bd47ceb02577240d84037dcd8dacbfb5f1d5048ae484b243b780d0b74a614ed5e8a42602783339ad3563593d6ddee1015ea05b405f4e99c8472431c1584e80077d2187844140e4c4c27b8cdcef7965332ebb80962ccd133e70bd0b206ceb24a059137e1f70d997bd344cfb4dab0d96cb64e5c3af00f5e867ab01860503f1c110ab3e5b92e71363e0efe38b94b806508a5a3e017d36c09dc4aa2999665c604e25bfbc07b2d7134ad7d700a886fcd3a99f822f3d7a615d03a217761ba117a67194069dd614fd3a6c3e711fae51172e56032566f9eda875088caaf9d5a082f2285f5ab33117b5b6f5af2c1254e20cc710f59fede2bb1cfa9f86ca83a8f04194fb8300e22cba368e9886388c5d4b868648a7a21a5dde2ffa720388b4d40b44a63be63a3ae0082a569ba1b8829d07a8d9b383c1b112a666cad768a053b534926768837cb5dcc2286543493af91e75be416b9a1c22f50645c902cc9194f42800598f688a992bfc048f4626cd3416c34b307a007bd4959075e095fd6e2d585a968cd687ab075a8a45146ba7cdf5df4e54c83ab383d4a68d065805ff7261d4626adbc3d843470fed256819d17e569577ce68a7895a4e37ce9715a7eaa1f25c0a0239510a7a566d4a80dbb9b69eb608877bc217fc5d06fae05fef7e077e0e842821c899f310a9cab9bb057ee37f1c38b9d090fdc281f720401c3750adf941e107c0a20a9eaa0dd9931be6f4415026b7f1ab738a3733d2ccccfcdfc0b879c096fe22448b5b856738903b553837083ab06c8b3f00fe9f17a293f41ca6ed3fd5bafcb03311928d80330217e54331c5761ed1a4e735c457a544ffd829fa461a4cb6223c61d3d2bb06a55833271dda29433fb3e5da3502fe3cc0a0e18036d1917c2f2e56cd0ba121bbfb8b2f8b5d468c0e172b8367575514ec556d4843713a7ced5a9ad98415ee2eb95352494e5c5677cc7a974992bb868c3bd12d8973ec9185452a3025d2628ed1f6d633586826f423d3f08ed938b6776ec4ee6d4a2e152c032e68a457012a4db9ab99245e4246fd0b045d9d60aa3ef217bc89fc2ea4f007ba5948f51429fb10853766dcde2663b6b36ec02a4d270434103235771a241bf23aa5d8d448cd66127b092ef4120d87b759868eb326f1e11a678239e75452cd01b08992e30ec501174be5abc4edf83fd8a493fcd83805a12892e857c0608d7b29f5b97afc7434c73ef72c65911d5f6767ab75a70d61be36daa18f15c33f1b46b587f185a72e2426aabd9b2eaec28e247fb8e7e777bcc29d02d8c068ce5c73167cc99176a62f9e52307537ff2c3d3c3c85425ff7fe563b3868761ab238d4060dd30497353fc16bc7cb999f7f8efe7b90d96d0c037837e8e88e103f1e64dd4bbcc7112fb85308be8dacaf7c1a7d1757484d0832f20440d78f5ad3df97364f74439087353238ecbcb5a163fe7eb7d438d2503684862a9339c97e7a4d4eb4443103b9b6e42cadb8ca915d1ed10473a85d2c80c572d8e5fd4cedb5a6f2fec1583fff552db41e3c838cac5f7753374587726b8908ef82b8d5c375b38b59d40c6e948bcfc5326f2dfdb28369a4f15e34fd26b8cfe9f74d8407c8160974cec92f5ac806423e08b64bc487073618c48970fb752bd84f9aa0e20cb4486a70d36a8ec5e1b6e8aff462a5666b24834e70cfc85b441d9ff702ddd0ad0a21723b1c083d94e6ff4a32c7fac713bbc8f644942d422958b3ae4b52052a443ae68fdd0682fb77893e9a12197a84b58ff0887be1621116b6ca2f9417433ee99efaa001be62b5106258ac8dfa083f07e6d29d23d559d7f134e7cdb8e559a913a107051267eb0b580016f4d12c4c6748a2796eec2bbd6f158d8c1f13827a03a802a71f740bb978be7388e43ccd33278580a3b7836ff74b873b89954fc91221f5bde535ee86346182d3db46ae60eb4566fd9d4648baffd401e9101c55be6d8896f70c4b5a864f5cc1518e76ef6333dd9d114b029bed9382e996576d376ca364621f7399b4f5c6118876fe863fffc207fe4a4004ac3a59576615f05dec6db2ffb407c268d1aeac0b53fe47607d0279b028fd3b6df03beeeac03a5bdf45d6ad6a5bfbd159bfad3b85e13728761a8177be5d039fefddcaff03a3fead8245faf821c3e7cb510fd31c5a3434ae714920ca7c87344c5b6f61de217f136770ec1bd4854c4793f4815579e08a7d44a3800f0716229912dbe501988c7913f243b450a855811a2282bee930c4fa1d9fad4b95e60a10a8bb5bbf2bf167a30ab57b0ae72b6227bbe7aa07fa12a375f555e9d40191cc0e6160d46ee52604808a6cfdff3f68dc3bb17a8c9a257722e9f19cc027b181b4e3dee729151b7802f8152228ebd7a0d50e000723947ca01ec98f9e7c9d4a86cc3f9de6b5890ad5106339a9d2c3fadb598339fe0604419b02ae0548a03ba0b92eb540236144c7d11cdd014d078ec86e766ce2fb0515557d014c24a3a676de6a537f303126a68b538c74803397684940daaaf9708fe4dac14f74443f688f5d8e93b98ccfb0a1b41045f46a6fd35fc347e903c4f470e0be2dcb1b389458490fe8d396f7d78180a3fb31523f7559491c249dc9f247af4f8feed3182937100032342a8d53a6b0da58dbe4c9f06f711798cd648d836f859ceced34fad9debb37878f6059a63610c4ca27988438819ddd749eeb01a5fb9831ee1f468075375a35b78bfef0bdd401a459c4b706f5df376d6d6ff914ef7a812c3caf004cfac2f6213ecbec548f2258c06973efd0142884178931ee9b03dd624c1899466b76074b172030e8c4775268e9df7bd951a1e23fc76a0399c9210d434fedf33e762c5373a30d7c9dabc6f4e3e4657893bf435cd4dac85a036a9059d624e31fd69465cb91f4f134e6a0ef3e97d7cc0c8598e7d228f00fa76d309762a66efa8e58ee4ef266b8b21174be415d952b312e10d6e4674898e1cf906d80cf77c452b27a14eca6d2d94c6f5fb35cd0bac568b9fd68da404ce3675e512fced6cf7eab2998d114d8bc0a84a9f087be1a88bae6b3594f66c598c8760678132130ebbe746bdaa59919c445f48f39757e71acd320feaa25c68e6e75e5e92b4aee84a84c67d9961fc8eb1ec3a347b94a5bbe75d0bdb58edb201735a6b953af03a6e7cbae7c6a1025979e1921d6326e42bb0fba02e37f5c2b88fbd202a731070536237f4e1db96688f8ccfb1b41958efb2cab993a63cfb22e5c572f768003ff3e08c16f352a9ca56dcad829091edc68d8328acbc11471aed938e3b4c3282024d2eae42ab1c1664394d35bced12b4adb07b71d9a985264c392215bd1f5498c7a8a02863f12c99d903bcff43c749e3d23c18e937ff48f749d0fdd458541832bcd06f4d0548d15c1fd711bd96b8e8b26e665c7dd47eade80396157386ee8f6592ebf7431ec13db9c07dae8171809d7111c27f8495ec4a39fd240408ebda62a75c3d91f32b4228d94370068c8a8f45a87b2d126b69924db8ee7894c7bc4e3e2c7660f6f0e66907046a1958ca8fc6e9abfca1c87bdd4ce6ba7671ae2ed05e009f36aaf90a42f81f12f5d2e7091488844aa9c15434b0212f1752655315560c17e88e764a06c75df4c6298078785e7f80485bb7b7e6fc76272f9e0a136bce5c19be1c2e7843b68a089901aa583fc1323b664f3810a6b2fc4999e220645c120a6a6687b6fd3f8ddb24b3743f979f783a1edf94955f847f7f5fcc690b539492cc9a5b105a3434f1cb3abf84663b4672205aa1e0114d48d731cf45d4a1336c4ba50d5fd8960228d9d0ded26d307fef38135dbb1077d200a70a86ef4d1ca51317cce4cc566a88a5015c0021bd35422be272a0a4338736114e4b7b6a1059036b48e525ef018ae0d4dadc35fbc3931f99b36f4b21b7adcf1c2ed52b445cbd569d9a99878a937009d7f90f8a157bec7df466d3fb8e0073d03978c99de8133c5c23c097e8fe2be40a946bb0800d822435904e811eca1f3f6ad1470dd7f35363b95ebaab44b68f651f60ac5a96453465846ce7d3c7083cf1633aeeed01228cb1fbee83a6dc788c0233bdc71013bf605c37c3cb55259258ff038e88d4b7fed663661374a9f1873930af777f1955887c7761213ebf0e25822ac6778bd179e4b1a252b0deb3f0098c04323500203eded723c662775ad22bcefa66ca693e2a529024f5d0745a8e63a5fe9149dc9693670680eca2c0f66b99a756a041b306cbb63ee216be8a9872654bf445280660a08252193ad8fa1d99624d9bcd30706d22d957dd401d62c97aa29d11011b03fa2a130c6fb29f20f541f2b7d1e0ee9c8a7a86e7596932d545e798ea7f9a000da078ca5208b03c82855bd916d8d0e1fb1d3c1e52ceaa7da8650eb4f975f550299ac5910e998f9a35191099baba9d57d2867ec0449472be8b3f1ab872ed958579a43257d35ac58a97d892a86106e0f754a3208700f9050fa310d719a07ff8877f38e739aff99d151cd5edebfde8ce51c404a692acdd0942a6ad9caba2e5489f6786f6289906f307c919efc2b944e3fd3a7701bec18279f21d06cf00891a35f24bb2e2dc6a1859311ed3ddb1a8f8a5427999f55437436d607b5cf52499175a9467c5352cb1177442449668a371fd46379ad3f8c1acb94fa10a1b3de6e6f6c2033bf18ca5cf6e2bef8a6c9058c3203a29b2d9b99f7f751e37c504b50df1fb983abbaf0bdc96a54e52fb3b1d14fd850a61fe34ad1cde2f07adc507c5d615379f1b72833f5a41224cc6b5d43a32fe324d075dd40d26058f0256b8687f81419623d66578a2ec112a90a12d4cbbc8800f8c06a5bf3d81c5da549b58e731b7f5037af7b54030941cb6223ba9f3f78a6331a792bed5f528244c58644e0e1909c13e4a05a62fc8080c94f53a05e9083fac51f6cef47d1a7d8f7d792b7188008b1b1c11955477729d55038b951d6ee2e59f2e964bf4eb1ee47a4cec04be04e71a104d53a726b48ffbfbe71c76af40ed9ad9a840539f64155887c3ce9999ce11f4e040d3ec220155089c492c4e7acc3498e5660c94b67a6f7a5b54cf57371fba7b3536c456d32eae568e208d300c017773bfd85bc982fb577a1db88c1b371727d09a944f04db70903cd83779e41704b9873b2e69a5f5ed584791de0f1aa3a173181e2ae6a2d2181af62d34e31478e9a7e98c5fa5c5fdd3197b676468b95ee683019283cd74ef452896ed0b408e668447c92bf3083ee2034a802dacdf15d5d4014564d07f9891bf2f04800b275490c305f1af1b0c8b53941277fa8d4b94f076d1b6043c7f5e5f9999df89d1b302ac251767d07d8ffd66734f31c60f9bc3a04f1b70783fd813a37b8a2df93acbed427bffe88b5b6f7967bcb949294297005cb05f0051d4ece0dee291ad751aeeb76b8f295c40ea757a587a2baba28ba9754c92a159b4d31c96df33a4dfe8ba371d37cba49377fe27bd2aeb3d4ca8eeb28d7c51d5e9d775d17ddbdbddbf8bbaeeb883a8c53f24ca56a7dc59e614acf4fe7ee55c98b87fd7f7ce8f2c4e1629c0f072455d27235f0e91627e38aa4aa065cc359c12a1f8f5f98c1b35c6769d86e3ccbc9bac519f5aa48d67049526505d7705cb04a1ac51a78256d07c61dd886b49c51b7585c92ac892f8e0bae893b38a32b6de7451ef104341205bc4ac1a1dd33ed5ab077c3f9ffae777bfdc7d7ffe36c379e8d3b70acc19bda346961b869b6e3b4ff89af89f372e32b95aab5bf5388787998d2d3bda4089c3a71e3c1c6e118b6dde6eeefeeeeceff1615ffffa5bbdb96dba1944915f790ae75bb6b21734c47772ddc05b24380ba71db628c42984cb9054599acc1e9eeeeeeeeeeeeeef68f31c6ffcec2bb249616bc64d48dde9ad6ddb0ea6d5c740ce3f2dc354d93c9598ac832d0adb5d61a43d71f5d59758074a4aa7db420ef0528c748fb48fe48b626ac5746b266b33c3d3eb1093465d307cb9d42dda23cb49483f2c0bad5752157d2cec093430f0f1f1f5e9009e4093165dc167649426ab42ecaa369b3b442afa0719ca8dbf252ae2593352d934888f25c69298fac1142b6da47eb7c105c4ac677afe709d736aa6d59d0d662b413b81f22f5064377e0feea9f524a893c8cdaa59b46e4afc34cbeeca298ade3eab89472cefd46b98d46e19276b8f295c887a257b3f451682fa92abdac468ee3382afee70a3ae4fda95d8e6e5468dab4ee41bea343ca583b167036529582549d78aacbe6b224c91a14e52c0ddb4d675d15474ed97432817a256b269154c9de82fb0bb621ed046a275cd33f6095ec2d6ad076706b42b148576a38dc09458b1aad6b42b91c05e0f219fc2569a486025ec92f3825a9a56da49640c85ac308dcb7acbd5ab0574be2562cf0a516ead590ba25b90ddc47923592556ee5747de07e0b7ebbfe825b9d95b6b7e02ee54abd6b9a6986515e11dcb711c4d96eb8907b45c16d8a1aadcc41b63ae6c337de02f190f9302262c3721ba95bda4bae5ef205843a2355d2b258b16ed1995c494b0404457674250542d1a55d1869afee90ca34f9d224678356e238ed45ca5501989b4238638303a0db4e925c4722091048a2caacba38a642015838c04f5753b6bb9ce40728d71ba2ca8648e6965e37e043a2ba6ca3d7b08d8154dfbfd8dd56dede7ee852397e0a72b923524a29ade8342436df8afc8b63a4942c3c1dd946298574110097532d63aa5b2cc962c2d461e418d9318cc3b1dbe1575bc6ff51f0f7523d59dd4ac1aa6e87365dd4fb265c6eae51843a23579235a4b68d766eb24626ce207278637bd97e0c37ed67933dffe2d0631c23ed86447b52266b9e6ff4cb8d56ce644b12114181727404841b65179265517d11dcf61ebe01b6f4e826978e98e33dffffffffffffffffffffffffffffffffffffffffffffffffffffffdedfddfdef1a1235dafeffd79488b29ddf6ed34397bbb534dcbd795c8816e1f98cd6d2f818a3f7b669ac624bab78ebedeeb40abe1f769bac61e1699ecb9c8c85cb40d870c71bf8e083542131efc0423ec4626cd1c174628ecc7575776f45180032869999b9bbbbb951603ee14d983fe4acb41f7254db0f255c577777733ed022df1112be588dcb3760ae8b9999bd11a6d7ddec24f494dd8ad438dffb638c51de58438d2a3606528db6bbbb636cb1bd9c56667c96657c2ecc2c31509e6bf354aa8539060c944b0fcb2cdd2e1e3333cbf0802e5bf931152eba19ef73fae134c33bc16085c68b8aa551afabbbbbdb6b4a087a9e53752e873827185c577777cfb9b577bbb72235482841e575c7d7e5c8d239a64ff0c184520eb69caa45babb39c6ff046f2b52a3edcfa9eeee8e8217638b53901aadeb792e5bfaa403aa1217b7dddb04b0971dae2bea6c383a57d2580ca31c184f104f5093264d524f9ec02887241f28f6b18ff5ca27c621c54e44c42a8c063bd745578c8843a854ae87d2eee626a7511bada6596abd9483862e8e27c8882c7c6ad3b1a040321ae14b3ae975b76bb196a51c9ba6195104c9dbe164b0ebfa1e5912c748fb1dd183e4131414141414e4ef53ebd9d7430bc49d4f7fcdb1701f9e202f1e3675dc1dcdb5afee76f204c9a24ee33abad915685c37e218a3265c9354af5838c00b2a28a13579d93a909127887982be1a6ab41f0d35c6681e5f96f69554cd3d86ff448d9bf78de09be1dbf1c9f0c550a3fd5e5f14c18b2eb820c2690835daa4962d603411c22908355a20fcc0073d40e2c10e74703aaad16aa1cac284a01c1845c1e20a28272b02e09daa1040b700022035235058a0c0d8d68c97aa5565ed088a9c5091333513648a3ea75bdbc39ef4141d7972655c8999ba654ff0a53dc6bee9538e11252400dcd28f0dc96325d88654428a4026ac542759d31252605752a4cd53601b525b8f51241fe2b21d8aae8c32a5c21fb30cd9953eb5a75baa5e756bab7181553c6a58784196e3051850aacbb2ccc539e19cb04cf6aab6d205ae198155d2ca367e61c500a6d739406f8fc0352f74abf2ea85c0ee6e19b933cdb068aea0ab71630f174de03352a3edc008b2bd3025c3c244903fa9aa749321e5cc8cb528a8a44a258f2498b2dddaf8c8500ec794592a550f6248b1a8e3811ec9c752f8d8b589750fdb1fa3174b5ba7e44a7ece5bd0b85b4dd95ea130535556d557740ce35a41ba51dbb81fbaf2a384b402dbfedb5fee045f2e920a66c870ef68430ac531961b65db0e65d4c84801070ebec1514617d423ae6bf5efd3ad69f9663edf7cd1170da9b1c6a49438b0fd1eee4897f3e448979312834addd9ec8d28e37e0efc383e8728236fd4b4e721575c102e08c7235bcfe373a69d3e5ee7797c0e11c7f3f03dbc0e9fd33fccdb666aa5f0f4e48468964baa417cc0fe0457b2fd1e8ce379441999c2d3add7912b695fe775528cbc1159e34a299252843ac109b9f275b8240af4411ef63e9e47c4f13dde873fc1eba4c43cc8e9157d70fc88eb8a2699fbf8b8ec3a54891e2edbe781715023514676a9073f25a439e79cf4892b2910456224e2a04758465ad904c748eba24a5c693ba4b15311b1d3ab6ff0eed6ada9bdf59efcec608fdc49613df7391c8d71cc86c4a7b0983061c262c2844984b1ec0a875b91875c492b7df070de402b457737ac87ec4adb52c6379ec88d9f477284648bcaa80c4a7551242edac46dc21483a4b20f4d2f3a932b8ae44a4b91c89a169d5199b3a9a3d861797b2c876fb8752547641646aeba05632d8c889e98e3ddbb1c9e63826ffd3ffe9b9cc52996a75b26cbf67b9e88918f3d8c6f74d6c6956c5dc63738cb1d7d62e37b2217d2a28e52cd46fa7259d6b8b458dae15405dfc8c71ec903f54ae676f83e3f6ab49ee57a38f926903172eadbb6efeb81a846cba72635da1814ee54a1d332c8acdbfd09acdb4def24458d964f4335da93508df684831abd1313ef34abd19e9678a71bd4e89d94d4684fb21aedc906355a8ee2f34e505496d3132d30eed62d9622e59d6a00a3e5440317efe4448d33bc13d08b779a010def94c47a271980de0949e89d9a18c13b3101e39d8ed4f04e31124e4bd468b7bbb12e65b2f24e4a98e09d9238c13b1941c13b1589f14e4800c03bfdc878271f96773ac286c77c8375d976365826ad873a71777677777777ef52a9f0ada5e1eefe3eeefd2ffda393a0a7e6ddadcde69cb2fdb559cf3967cf9edd9c737afb12eedc2173ccff7777ef9054d6a36d7c24ead04641658363a49da50bc4cb624d6d1b00c7942e10af0d129b0d84e14a36c852254778a28e4ae518f9489ce0945cf972797825edb6559e21277852848ed4088363529e8cdcb9c9c3f7a69d27040f8c07d6ea84e0e1a9cee3415e0f3592e33ee0cf651b23293ed8325252876c7f0a7e9d83912362ebee342f96da3ae8f7857044cc6eba4f9f3e478040ca8f109c092356bbef876046d6dce4b6d33cf2ec5e7306d9ea72e870023108563a3b977244671991a6d6eecccccccc5d0ab8ed695590f26fbbbc9d2e1fae106788632359ad727b3337c749dd4cbbd9096a6e5590dbf469b539e7b41f458c45e9ff52b814b2631251fe9c433f243b32e13ede030b642df5c42d1568ecaeab4ecd264a4dd96288a32055dc10735437294a503b1bc82a6a8909195413911d4184081104e63d417ae82982cc10440f7a8031a27e53c426d553129f2471a27e45482e766208c28711049103abf47292206a08835002976805902b2293ea97930079720590a0bb417143cb55b15000168a006142c2a0a65c4e02c4880a840524f88a00240551809040faa8d58b2b6a0b163da49686a492243f925c57f522c90f24ea4e921f43aeabca1924f901a4d69ae4070a6aaa7ea9243f469004e683ebaa433d7553d23ef54b2252514b3d4ea4962e278105b144455d4e0223c1953f9525c986aa45eaab2a14808503d494ed8aaaac1acd66b3d96cc6840913264c9870b36f1cdb940c59b35190bf9dce823673bac0ba34286523572eb0ae87305215a4eae1c4ff5f1aa1838728709038c1c112321431801c4c6083155ff4c0210a1a783126a7231e4590a10b26848e7e80ccb04309901738d0e06505caa150c17cd54bb1666c522072ef93917239091050dca68117c3818b9999999979d37cfe4fd79a9999d66e8eb9ce24e57602fa0dd568bf99bbbbb7bbbb83d3dfddbfef64d3ee4e9f74eeee1eba6fdf911a6d77d409333377a3f785b6512ec6d8a4f34aa6949aaa757b3b6b4ac9050ed04dc5050980565be94c2556923be5dc63ae12a31bd73917d53d0cea643c14e8e69534d72c4b6e9a974c1e644ad182a2a4bd756ba34373f3a4cade95f7c61ebdae631762c42a62a1b8c4ab89321654784166c2026e5b5e3d777f03dc6dba645a299bff11083d8cdab41cf48202b845dd6918614d60b208932a2611b681205290acfa795b0ba374772fb510de8e91aaa8b5710a1bbd9bbfd882075138e5abc0b769d35fb5b2600a0665bdc00ce090bfb892b7b892a35c298536fdfb25eb5669dd2debcd5a5794ade0dd18030b1893947f9d8e9a5069a21b87d93f126bc372eeeede319b4b34ce574e5fa434729d572a791d4797f85862c4019f4a2b701786252ca89667f94a2e4820b45a0b9a6dad2546aaa2c2d67b5a41dd0e460c8db5f20161d85097afe40205beefb412c3a59f3addb4d9d555ba6b2aa5b8c8d0628c81291895d5cd4040ddb65aa3b6844c3159191eead2a06ec62c79321411ba28db9f5d2cd5ca823694001dba384a6725cfc01472afaa17c25430704c1253f32adfd80860c338b8c559695918c7b42d976d84758b0b61e0265c110683540555186c06b8a6c319a99aa95e46969c69630b36e56e71960353bd22e22ab3dd361bd60a8441bf0a7c14b5ba6cd32d8e0e6dda7c2f9998f53c59631a61bd929e4a85b012ee4718ba35005e49cb4a22ce50514441773bb4e11869bf246cbaee5293775dd630ea7226190b10d9da7c8f3b88407ce3e5b2cee6c465bba5eaec26b4089439383aeafea943aa5695b537c4000644b0c0e27d6aa95a55d612f98af888f87ab48d725d4f6ca360bce922175337fdd3757747284c58a964b27b781cfd24f133c26ca6c34c87d94c9b4343433fff0f245572f683b7cfbc7bd645d7dddfdf5e58cf42d336bafd88e05fae06bedc0864c76cc76c6868289594f453b37ca3cd94dff4e410191a1aa241a51211a2ed981189724444488c51ce1891de616868e847889fd96c369bcd7eea309b433ff483fbb74f79f43fbe63b664674a9ffd337dc72c06a5aa9262f2baee87b3ac6994eb381d2a437528c5f415290d75f253b063b663b663f6bde01b02859b82114fb8916fc418a3f709f1b9e06bc127e4e3f9763e167c2bf8827c417c40be1f1fec03e2fba1bb873e157c3e3e1fbe147c3d3e9d0f05df093e137c3cbe1ebe127c3c74f7eccbf976f874f848f0e5f0e1f0ddf0d9c0524ba23ef5757a25ade3f85c498bbe47a747e73f9f1e2983b78c4926ac54edee2323114a54ea718b03fa8072361d0ea85b2c0001b180457571441c51932640409e94dab65a5329bb42f72dc140451e031223040505057dcfb75b16e298233ea9ea3112ad8c31cecf833cdc993f3f0c57dad20840303a2a1e4ec573fabea65d9f4e27afe32c4521a17e804a6a39a2a5a725d6d204aba295615d322ed6a572d9546d0172212a99b416d40f5048a8a496235a7a5a9a6889c94025b9a09262a09260a0925049a8a416a02b2d8c0b51c985a8c5aa203596528e16ad8b25032c61296199ae05847da51c6e57aaa89185c69752ee0c7008b71cb8633b10036f35b05d0c68002584224239e14c3297282e452828a828b4b3b5be18a18eb6995c4996ac4617350acd641c83128a386894b08c131411cd922b2d277465cecb247389828a8282424db2ce246b932cc52433c94cb2251ca3843593aaa3ede83e092f672919c06f0baa9443b32ad1dd743b7007c672e04e0d4bc19d177086215a2c0ddbcd0eed8b2fbeb81902031aa8b7b30da362a4a2850ad2ca9095b8e2b3528455d146199654ac31aec46e8ca8590c856a80a6995cbd4903d2e209235943e304ab244d4e920a52b7687858194283b3c2d32d9a18568a74b1e2236b66861093b92e152415247b5d2a4825152415a41e2a48a995d8951646a8080a6a1671b0ae8882fa429a29745d367084584b584a5eac24a423a32222d6ac2564c938860419360d4ce2e1a92d2fb6850377606c07eed480a9215150624c7ad58071b7da4945c6312e4557baecba6ce4b09eb08cb4238036706cbc7a950404a9425a01595058451cc31262cdacb8d2a2a0441c2c254b220aca0c59321264a82e934ca548d6b86d1d97a2ad6b6179210d50afa41f79d2c77a7660493cd840c382b54b799db515a4d101697038c6d2f4e09c4b6303d3c4c0335ca09d23b09f2422847a25ed0b0da3238e61e98007529c64a7528e1d1ba39463c7da181cb8632d057754688038f66686f5a3888312451969699154fd806ba815ac62f1a36e51a33e825bdc0ef7703d52f54d700d67848cbd499c0f158a384c4f4419152bf47224bbb8bd80342d200d129a180aa4f1218a38688eb00cb582c6c82d9235343faca269e24acb1212cd9cc01923a36ed1c4c0ab1d9e49bab06ecdf48057461cdd1e593393839e2b2344f5e3ca1e57f270258d0dd428e7ca5832d6ec898883c58415647aa28634af1723d08863a4e9e5e88b3c3535a3822fb7439025eb95b429208b0864599065d42b69bfd20a32770b594245f505a401d29001d2084de02c819163a2a501d65e45af8319a1a4852f4635bc17f07624d000fdf5ba6930c6e5c09d17dbbd845bd712605c0edca161298d707b7999317fd572bb979719f367b8bf20bd18c9587962a2104301b05d0d7419ef45bbdae6a1502c3f5fb3bfd9d7260b4b0e972297227f9f2e453eff745a595951a92f46d2a69472984a284175b15e8caeb4ac2259239b09b188648dab026df238c554a26991916598b85bf3ea74b8c808f4e87eeb1cb8ff27206ab41e9ddde74ec7ec77b656a493de47856d217687afd2bce9a6c9684e9502ad75e933ddeda6e00e6737b0e9d65c683313c5ccd3f7a9d9ccccd8cc1431332711ff36f859fcb74177f7ff307b1a4aa526b57ddba17603bf65c338d83bd46a60b7ed4e859cac4637d1cbb2864b18a2ba52574a1822d42e7916bcc2d40a191646965ce4eb86b3559e2e05745cb6da06ce4083cd0d0d35bb4d70b314c4b1b9d92c07ce40c376b3591c9b1b6a3bf7f72da42b1c2dcd4e9b5d49b655417660b31ac83754b32a74831748dd8ea5046837032917d06e871478cd828063371e98d6039aad975da20b872f68e1f52235eea06307f8bbf3901881622cc6622cc622a59452d125179a6e9a6f2517de76b8b577a81d5182df38ce6e6f39ca6d94d334d7268fc3dc27cc7dc2dc27c7713bdc141f468e71ab5d8e6e53f88f86c5c60d78000bdd9a40af607800d76480526a350a4e40da882333c035110756495b14777835846d48db816ec51c59531383a044624023837825a3939c213cb0188b3118cfd387c99af9f329b57108b7e80ea59bd62914806ff48c879a4f9190c776223bb2bf3a97ed6a2bdd5daafa49943deb99ec9ef5acbd673df3ee9efdec20346e52ebe9ffce3c9bd989f4ee666666fe28f3d2a0195d6a9135ced4a17357c7bbbba5101d5bd7eb35ef5f2eb5105fafcb29f6888e61da4a295f06758ef8fda173cbc8edf37fccdc67019b33a8940c9bcb69e766060002100083160000200c0a060482c1304d03414bb5031400105d863c6e6038190823498ee328888220888218648c21c4208308320a11510d0006fc95527c1cfca1932aa5647a00254f0843b0651607b8ef5cdad1f8dd8de36ce92b0f9804fe73c0b0e6915a0827cb4b830d4e288c243cde880282a07ad13ec6969e18e131c09fefd9f9f98e4fe831fd1df17e79942f0a2b067bac886d287e82888882318123570b8cfe05bae3cf0a0939cf725e442c73abb42047da94d542edd38e6d0195c98b494e2c9c3f9c5831202c42fd0693fe170f14600381c5304007825108580c0045e0e70c7efd420ccc7e7dfb3c6b6f1c0a50fd909dec21240d98a68fd17dc6361182598c3ac4325e9046d75af161e8d4807500e80a1889ec85bc40d791cd9e3abf17d2d4089f6ab7b4a9787ad2d77004177bbef54d93bb5f023c55938e1d2a87593225ca155f07013cfda95337bccc0a1d751d5fc271bd0302af151adbf19770b8c5042c20bb66c6cedb689e7b1bd896b3ec51be88c9fb4ef764f2162ddf01c6cc44a3b4b04feb192a97daf83da7f9c6c13231b69c9e0d21ade7c2c741100040488b062cb1cf4e46ae8ef6d6b2b84b606547cf5ba91b7711179213735138848730a009ca2a114672287cbc4b67e08431109ef77f6107040793a8208b443cff68d608b6451a1dd0065227cfb87d23568d38a2461926ebd7a0466235fb413bb4342165b1e1010159a7463631b480a6b99b3c35df8b665477114310a8ac8a699bfbf4e7431fc2938bfe01480ab9f39f943282e654c2502f4c4e6ea87446b03a0fee662ebfa28843bd534b085a720a8f5b28a06a2bcd88b011526332be6e235488e4aaf7372a08454dda58b86b5debfca2cecbd3cf0e7e19b2fb330b18163e865be8903e33d920bce73450eb8c4b350315a7752f98f13a289d3d9c16528b8b0dea95a1391fcd8bca5e6f73ff0cbf756c09046dd3900e22c3cd4b18520dcd29b20b04384713360395c7fea3f373050abb99a1f74a951a30b58fc806617fb0a9146f33b2f9ab5bac545f33451a048c3244356763e1d0266a60bc694370385c987f96f0d02c0b5dbe3107fb7fa5562f589df162b3b22c17f38f3c2bee8110ea04ed16016ea2641ea8bfe066f6dc8f61715be3b85ae07019084c6590461a58492c76531b5ebbc514457fe5da9e18e3440844d7c6c36b9705463ecde9d571369e4d45dc701a4caa48176801c023be0d235ba252ec553314ac04cb886f5005c6b6f7ed52512f58000390729bdfb6a03c447e6218ac50991ab380de8a615e943a2348bdf39879d22a23df4028bc8a00d009462bdc21fe152532ba100c16339601f30f0418d63627512126bcaf2a52d5f75bd88f21458e7b29b488372feb1e13a67a99cb07e3863a5e90f401a5ae00bdf5d0050014aa8c03289bc732845e292a0cfad2085288f5080c9b31738f0fc9d9b8e712091198a653be3614c137636a5ddc6d75dded523655f3b15542b50a01db4c84fd1e682bb88076659b7004158a6ea3d7a159bf8cbb3a5569dd190cf8916d2e22ea71d8de0d5b00d2726be84cfb050e186aa838a496b35bb1bafdc57a07b22c990fc7dc495046a9a56dc96c5587019210a38d7656c5d7921f3a6ec01a492385d94307c57575731d4cf6e812a7624099f72b40137fb7d82cb5f09c9ae070bf095218a5e8543cbf0d2b02698e1fa520471005158b03ae2a476935c735db9feafe0b5642a5509c207c2ca8a4f7ea22702c3c0652ff48f0434f9f0f20aba52247a0642f82ee1312841705649d86bd13759fc083e381800f00267735e221a412ecdce7a2191d4861d34ac3ad9c8df35878141a5c96f4f78eee9fefe2250bd2e60d9cf85a61910c7e56b9137f5ef6bd2b6d27d01d2dc4f6e23a2d1e4fe680c0adf4e0fa0e4f59a8d4c2f7e3faad898cfda6c339e3c10f5da2ba7ed55978509f0e02102e863779e3a728bc37a38b551130239ce547487615a3f0409a07363d80022476e8bdd0b53f53f511794b62ba1d4b89f0c06b8a0280416e758c4f151bc8e3a36317755ef3452707f38ad6b80a6768043b6af093ae6f8fc583b6ad00e212a31143168e6ef6d59400dcef18bec04334ff932432b8b2f2198a135dcc6d0061b3eed72d8a3ffbca7210415a9b850bf6f2d2b1ea8a4655b4ab01885fca3e21bd34bbc9e94ae9eb7b32dbb66de7d411e34d05a010f4af2c2890299f408e5e067e62025350c81c06cf631c48ad5d70e53567ce722a23edf14d11facb840cf34a84f6476862b2ff6d75fc3be37df714a105d87ab8a12e920830e613b5c1016f705faaf2c59fce221684aeb6090ad9c83105a819bd3053d202a31b01800d3c23690fb2ecb126059f95a18d4d6b4d9c96e8a24af38b32b4ba03fbdc0ce32bc7224d2b2d35edcd40cadd73ac68b8e3a368c41ba14b2d6ea080b5e755b88bba4016be31a944be3a69f46501611bc8ab132bf44965387a26a6449b3e6d750ae18483c0a7e2531cb42eb4e5a552631427aff65a1a220be858b32836dd86dfa9ef561042f64436b2a1938955f2951229a0c27180c67526280134446cbabcfc8301fe05aa18d6b23c5035124ef17854e982cad178ef12c37d09ee9eb23654c9428b9b01fab58840a1fc0aaa91fe6be65027e6ee764147cce4e32848e201e2721530d3458453a3a90c34389fb769d9ce863f19b8800a304ec02c8708846076696dd014e58999411e10e07e8550f696dd87ec6d94f6cd8c6a272c9ddea0a83687f0e477322577e34f66cf7ee533d703c47acfbe80e747bb9d9d995ccfa360de67f049a7f1e949e8be59448b76918bda4c164a49cd9b8d944f77d2b60d75d09d6fe4c9f8f2fabe3ae1b7f53a8d23e381543b236abcf754f7de5c7011c931a16ee8034b26653d7c4377bd050010fb020f3f83315658da4b1f98dfcb865b7f24c5bbc8021d0f700df55d89da774e4a5370c624468502cc23fc0aa0aad2b2ae9151bfeff844fd4e5c85bc0948ddc47d59eb5b5aa2a469ddc84decadd1ae982c3e51f8f167438ca23af85fed5ce370b5414e5fc1f8c4ab1b29258e5868c5c3da8e555a42dbe10aa374813dff39b69c77543f205cbe0c14ccbaeb9b722989a5894b2c618cab60b1dc9f5eeec9f18904f3be1b85c42423fcb79c0e7fc90492ca0c50fd70f102c69b209b3c08824cbfa294f05941ae29e903918353dfa012bd4ea28ac7f19679a5fbd49c66374773e2f66151969c94fe2af25012d3c53ac0880c2a0768229b11af349e8e169b84c2df30f47fb12428f58c27846f518f2f8e05fb810b483d58b4d1b0996e9f9805c7be1353e36be3302e218403a4b5a4fc614348839bcb91b9f0d5a4319ee7b4cd2d6f14108657b7959d0b185575bed5160ab42dcb9d3fe1e472099f38a9e807c314ba297c53dc568b5e38f8f19f23aaf4eb7c633796af6f67ffc8d7f70eb1ec6776e9d541bfbbd821a70311fb3d0be2e56b70cb0beb2a85f0380162e3413902a6215e1466c657efc6b539040fe2db8f30a89b1a51a1bdb8d0a409c5ccc4ea6ac510a1177cdd2b33e101c7c9e85cbca830c80d83cee910c6c147c578f6075ce59a2f419229e4a468d0fe3b351127f73f2ee32968e9efe434e0c788ea20702bf494242cd5597a7092b81be1ab38651186780786515e5be56e98f2a8fe5bb14ad179e876b5c7288ab31aaafbe2ec87ccd1e374bd0a42ba3bdb60714dd6e0c89773ec442af79c7e5a363c8d0a6046e4f4119200aeaa79c1e798285eab6bef8524877a92db94c51c31b55a891c7a9623a6d987a191e6e20efaec7099d7f397a4ce922c876d3bb726860566a9052694ed02936594c3b824ba7aa7815106a89dfc2b40d2ffd6715cba412c620f6cc24e98cc9a4ab478550bf47358fe338a6b753c8a8aa63a9eaea8eac88289e1e7987db4c90e7b29a9b477642cfeb2aa56980a15bae0b4a5f202a3e7b570e63ace12e551a7cc761ec3948294a31dc53d06f55a19025a14d8a4b4259811fc6194bbb78a2e3083841250f042da2ab0bd375052c448de45c1f2dce770e73dc3dfeace028077d366f7a05022f66a8f69048a944c48d054ec16276c41e925f1958236f29a0de6111317e47470ae6cd981477158921b0a2c90d2dea1f8f8923738a7b0fc7542a289885f71ee7f6e92c260ddf000f69adcd1b4cb0ed486cbe5dbc4551de3e088a823aeb19a2b7341a7809edbc318c39816c0229d15af810c046762b521132492de3c83636a20baf30da4a7f64986d11a6320dc7102e99f0b65ec9e91731f3177e4bc46cc51457cab44da2aaa0b21e2a14a50bc6a2a65aac6c7a4f8d9786f78dd52c55664416cf651a1ab9bcccaf1e30133f2a7d6c4eb0bb9fbaa2cbeee13179142b9b9af36170a303c4677f47843cf002f7f31d23723ac09335c2286d7d4cf54990b2b2c2c71ac0577bf00bf6fbfb66d21bac5d75c3a83283c754b2ad8c2a825737e93ea19a601dbd91de472e08cb90c1e34959acf5f4331d1f82e7691527dd7ec46504789caf74c7ff03a97fbd9420a85ae16bcd0a9b1d3ded47f8024c0020375b05970e4d2cfd2cdd8ced9aaa7514f8350721977a16fd83dd00d024cb213952a6ff1476da030ecd696899b44bcb763e8b5a958d0b5bf16798be43fb19a79322b09f2104fa52840b4a3006665669f6a3f4276e9568c042a1e7646cfcc489b183918e519d5f1b8e58cac95fc7488bb8a4be81e429481cba776ddb69e195300ed90c7d5e82d02fa24209d19615f4f75d69714b5676e81db5bf06c0c8c06f1a11508bfc3208e4e5506d755a15576827b84ee7035f8dfb15c14b1b030c417d7db7f60fd494a55b64ce7b5b0de3aa635e43a11cee4747a976688bf8012aad3848398e8bc592a72dc05a272d8daadd490729706ffaa9794e8ef0193457b1e90dcdc9cb17f7b4265ad8e146a41777fa9410fbd21a19ca474c6a79fc012408b711324d9c1424f74b79d23ad260d6edb09f72c04221bba5b9a5ae546b7ddcf530db26f60555fcdb91b048d6321bf43e15bdb862dd8d63e52be1158e86b065e1b402a472221c3d6997b14bbda2b0d984eb97d8b94611e5080f998bf3f21aebcb9d559a27db5704064df9e4babca91ceebec256e38b630c04ffac9f219dcffc836283d6580381c05a56d8264ea35f1656d9d6ac65fb6019ba960aea4cf16dd269082a26b2b575a389ff3324a367bde12b5c0140e6aaa263c19dacf5669c1c6cfa286e530111585a932628291be8e0ec6ea06b97bf5697327310d8e208f289ea9b4d12d643b8145a3239f337e5b527d7c5ec29b6b5b26e74128ae544b31721fd7e334d665d4b28e1fb730ba6e670a7c17d0407b8066e6d6b410951f5ceebfc9b2cddfc308bbb44f3859d3840591ee535b6741d98b0c09644512951397ce95a07a398ac50e8b6cdc56a5f58ffa89d07120d398d12d1c35c94baba9366121b65145f7d81c7432a8c2e3a5325966084674ede8643a3bc4b830f8e9ece81c9ebe6a2083fcd8cb17c1ae5e8b69f8c8ed6b9af1652774fc61fa25d64c979ef64f66813b5405c4d7f608c582b7e283f0b178ae92a621d231c2c081dbf2bed7aa2c188cbdc64e34fe1f0b03711d4d9b86a47c5a8ee4e62bbf3923204553048cafb37e374b3d910fc5ef31d94c8fcc04c5f504f360d57b0190449a9098082a58094e7a704a321322e226d2e01aa4b6638c5ff53e9cfd75f7f51d925c9429b706322ca2b4a4166842a8d03873cb04b8966d8027ac9a2326302a1c4202017d93d60a19fa81b6cb997d0763c79bd200bf3c402425fe0ba0790570829897227d8f399e27f12a27cb9df21ffbcc7271d13fe6a3058311641798c21ae5dc10d87678f89c8d43859022fb36a0ae3d8de1cbaf49f502d98be9e06928e7cac0e1142c5cf2a5932ccf2effd762001760618b8a5ce0a3f6d09965c52ff339b73f762e7ee1ac45259c5d4d33ca93812fa96534c7071527209e2597154c3dea3c9fd321fb9ce267be13100a5cc03c0f848690b7df7a69586c7c23fd0e6032cb52cabf0ddb251c42b12a8448bc6cab69cce121af090e906acb5255d3473a4715f96d194b80c55ff72ad900762a6143a39a6e6ddfc583090c90cab13cefa352a98fa793b2f2b58da598abea4678384b5a6c8f12670d88a6042df03e793775a30e76d8a14c153e5dab2f9a84c21ab33b72b375b76bf05a29e91feca0a2fa7cd208e8205a581145492c7fdcb128f79edb4585d01602f940218acfb63681919a1bcf4c7bf917c80ce723d19b18a66ad042cd11c6db559c850fdfac7b71db9f47643031d7d23980c1881ea4e260b84c2b80ba0367565e377de4c2f9c2c10255f8be1fa72819f55443ac77ff1066ad0b6d3014ee2a5afa36393a2c9dc86e70d061767e454b05c8d5145d9cb0206ccb155b37e2d34471113602cba2aa073daad6ad6139f5490ea6908b585f5e2c898006a9fa8f8ff4645e7907a1d18d4d12d80652d926f5ef4b592efb2454c795c7f577a4c7c33693c8d1ac7a15ef1f8baa2aa3b0b8246502eafc742fafd37cbb6ea23fe2bb15983efe850f8950d8eb80f5b99cfed027ba4dc3daebf107ccdbaee84c9aa10209ec25bb3713c53605da4a7ea704e22bd4fc94656d18f3965d371b81c922bbfbe2a1e40819275a41d063d09310414a04477a287ddadad4c38f90f5372989b8ee52ce64c6c111215c7c8e33e064f420f245d2191cfe287539e4cb42d43968ad703ac5c980eb412f66cce0eced84edbe20de10e2975a112c3fa80ebecf6adccceffc4330bc8acf9a58e250da2413d21b2795a9d8d6a5fc52ca44a41878931b53ac32ecd6d55fe6e3b50493f8584633cb88d61ef2be59dc555aba45279f94652b281569ab44ce9b272d2ed92c6937f7fe96111d4cd062edcc3947fbee4aa0f60b0cab0264429bc27b1ec36599264df7c4408a40a76294374c2e2c2f6af202fda9db222852949333273ec1f9868aae2be056823a3c1c8069c445e6745b6aea4ee0bea523e1587a4869be5d49e2927e7b7a73a8b02f2d0e4eaedf38352a14a3e6dd822aaf10c64eeb93ca77e4b6e29596a90b5d3a96c19dab5f65b365e84fe70c0b2f1d53b7f80875e507f34351dd9a323a34053caf8d1be2b21d120a62337a4b4d1b019e0a853e3f3d22a97a10d794e4e3e9814dac874541e2d3dfe9b9c6ec62a5ebce782fe6a69793bc6ddfe9195a082d17080d8842aec95f0969a870380a1278b8f4b340de30a56a2a594fce59e5c8a097ce71433a8bda89f0f8bee1431e8489a5ce016a0da78c82680a45238cc17754aa2a13d6ecb58616b45a5323c020f3caadbe710931d17a48664d6dda35a254810c0557999f47fbaec34990a84a3ce1919ca26aa825f6f9ba3bed912e6de27a2e4557327e25a5a3201b15e37fa9a07bb879ea6c9a60977260e359bf3c4672aa215c9cab124dc4715a8aee7458a46fa8cd514b711929fe95c3d2bbedd9ed4c5fae5b8ca927a8640873e5c63addb17e7464a832738cad9f88ba6450958938e37c14027b4b0480ad7a92000abaa6e820ace299d8a226b9dacd366dba9b117f511e3d4c6432ad963313af25737402a04df29af183f92eaff7cbbe4d93548df47ebdeb6fbe300bb86b7443ac98a8063c3bd9d042e8cd5cb0a5d39d068b8e700a6ab46822778fcc2914fbfa8681f45eae3aa35184ec4b8915895447709fbe6c184ae766ceaea9d90a9688ef9629d0ec49ef6bb5b286e166f92904d2d94b47d508871ec5e8977560f1f5bfbac04c49ed3ed2648c12101de136a8ee1bdbc6bb6a6aeb040ae2a1e728e5f2e408ba1bc4ffb61c47b65cfcafdca7e8740bae094714ebf196f793a5a6b1e6d03be87492863642b751bd41bc6e444107afc1aadc08098967f1ff66ff78b0887ac768b51829c6a148a480f1c17cdb688e26d4cc59a90a5803da3c0afe7f2e20b6b8c5fc95ddb869436da02b7e91e683dd7b3242291d3c588e1971916abd19daa44c42e76eec43cfd0434a81a6093e89024f039af88b146807d24a6a15f5b2fe0204ca4861ccb9b113e3f4ffbd6da765cdb19d1e3aef5a8c3c685d917634016b0a9b27ad0d6f44e0e1dcb70c9ca2796bb50c6adc99b2a892e0ef7f355e10f56740c276590638b3d65e2a63b55016aac2d0e045706ff940a923c642084fc8642b547c558eaab85703f2f013ec86f8a6447b8a11fe4a65aaf8274e0c56744cacf10972bd52461398a95275c67eb21e51cc0ff5b40f2d07434ef513bcfc7dd3e3f5a39586ba5bb9103a79f28b89549ac619dc6d4c326812ae6f5b5bb265b455b1aa4e3d86238012e8fb5631850b26e3e2a40b12af7015bb5ac2accb5078c360385be42d85f0eb616ab0ed29a2b0e62bf35c799ffa4d3a8b0781187a8d4d06196feab4e4f54dd3e24f885647430be6a8078ca9b77ca90aa4f87357033fc5a08fc362ef144820418444f8794968346d615c8b3155b07bf7010f718ca050ecf3cb5154142836f69b081d8b2ea3f2b4f48b9822126efe0a24a8a2d5c16b6fae0b6b902991b101d455c69d16babf43b3160e640af25103542ab11c11dba87548d4da4817eb28830a8a2fcdd88c21c8665d334869f98e7465e865832ee718665354f6fd8874d08f88f6aafff15da03b95d33fc585d296de7b37957a4600d36fb6a3cf34454b06bc11b9fd952b7e5606a7d008d28c6ca1eecca68d90dac526d7a59988117c140957ea76d60162b6ef00551324b52e7b06216edfa920b30b32ef2a67bc5a0e19ae1929ef8b1613db73e545623053cba721722c4eae1e39c5285bd729e5e7b9a95ccc051d37ec5b24563d69e46e036f5e255b0528eea08447fa6a1b240fc6ffb7ca936b888c7cfb1afa64c9cf5d994fb4ef113b13c8b421d3037318c687515d00b4a6a7fd06807224a9eb6235cb720a1a019ffe9f1c143c401d3803d9ca15ac1c953e07fa77802d04c01853a7da2f2e796ea04338766d90b335c2d1e6208c9939b0ea4400be823570fe9d3c86dc94e4e595e5d2e5394d6ff54cc27f31c7cfeddfc0fb301ccfcf9943c5a836349680c05b7ec531e54074ee30251cb1a2883860ad8d9c4106cd8cc84d1b874033a3fa1ca7ae02b8d46e0c4503738c0747a076ac351a6e0440be565732587eb552fed23248e33c72163e575a46bdacec8a55d27f6bf107536d63edbd7e939511e0f17a27ff037e00cd14aa48093658a337bc080e227c8858d7673e8037c1536ae0db14bb7378d0c9ba40b5b182270ac3014094308eeda526c6a187aea8ff5284e94814071ba13486d6ef02d2dc9514d22d4875ea4394a8daaa0ab37c2046ff378a844349ba619fa67cadd8868a0921602564031d09bb5ebea8cdfd03ddcf5275378f6a5794635853ac7d7569cadc7d4c7f301240bc365ca40f3fc5a8b9e9392fce9ec10e1dd8284bea9355083fbbb873db1171ed59a3a52ef69c240ffc950bba918a4a6710801c0592df1c8fac3fad66e6b76a91a25199e142e0cf0592c3289cf07637b63879f7eae6d44a67b2144632c0c4d2b2208c14da16bb7a67f68671eb47041e672934b4ac8db367f5b08e35526d49eeed700ed516b9c1192cb191dc784150b528f05c0dc3db6ee010f2bfb1c45e12a6179ac1ba5745c740e19879090d95f02f51ba9a9429e84fe54a6f077bd24c8ad896967e43c632b21029a8f7c4ed0a5cb67856a0cbe37d6989a5a594c8a4121256460d0b20d4711bd3f88493de605172612cc362e0909ee206fb49443e21cee37fac62060ad65ca5850499731eb5f63ff55ff65cd2166149ba536357250373ea52e5d5495dc837114495dcfaed54094ebe25653359e02bcd64d56a52655e17e40b1c3325b4d7c080ab26cf19d6b69ad8e2aa14172f49a0a95c08cab47d9b98519dd274fc1350aa56f56e2208b1806cd253a14d89dd2de01ea9e43e8ceb8ba08490371af6491847bac5e1da763dac6fecd97d196f57090aca8ca112650ecba0b54f39815d1f925dec5d28c6d505f5589f08e4a2298dcec335b8ca179fcecfd5cf0a60d985d2f5e1f143bf03f074b08eb6f83e22ae879f2a26b95e322893c9726ed3fcd23a232efa9e9981ceabdce59fc193c8d36d5f33c252413a62dab828e3e81127f19ea5752e242b51ce04a8f09310045a61f1e8fb145c8df154fc04d4e986b9b70514ce8931ee5d86e23dc90b4358ed27bc553656b641a1a1f2241aaec1e4ba8595c727ed71d80192bf8f5392be68cdcb126969dac9c726b7a5d32b844faded3eafb24ba1390c535c76beb094eadfd3eb7c0336087011c13eb4f084bda017c8a9b2521d884e5a229a4885843a690debb8e46f41355462e864663fab89f5051c9f557e7472fdce2a3dfdfb962250844b4e51d7eb870e9d95a23a4a73b03b05314e4615bc0b8d17f44874d9ab156f8725b73336a387ef4e5b1f34c95fe0158e4c01e099e1d4a4bf0785e4fcb40157ac40d35b075cfcf3bfe79423b78f2b192d9229b045e122ed40ee1d5652897f75c1636dfc5a3d7cfdcdeec5f6edd4bfa6edf30fdc2fc8714fa2138a774a3f822b32b950297c02ad87917c283c115d258093cc79bc172cdb0f3602f65b52aa317f8b102af09aca2d9445a83083785acb460743ecc467a01c911086f70bdfcd139102fa2e46ac8d4f9dea40cd1cf050bac96dd9f05db7811c935810af9ccefdda08edbd0505b7102c2717daa89b21514e9a1674634375ad1e756c0e23427154e81ff2ecc5637bbe436f2a27fe80a68654eaf925625bbd59ea555a2d2709289de1998fbb6c21029f0d8caf8e9c663665cf37917e7ffe5296a139a4d412d96880f81b784225d754cfea44e29bf9af69c4b4fec927c0b62925db3c4d00ef63cfda3cc535987caed4d366726f2485058e6f69bc7aaaab9143fcfc33013fad601cab8d5cc09123a10a7072213119790e7311ab480f486ff9be7e9cbc51714c7befd8d2c05a5a50cab2a8bf9c39f5e50d903940c64f575ffaea780aea81801af5a71403ade992e37facd503e84de819c3576e2b0974b25f886fba1ac291114ab6ce9911dfc5f12b805a3769670ba157e35605ecb6a46a82bffed7afad5ffcde57dc23191b34afec357e7aba5ace8fa8673d4ab9ba60214d3e965b66addd6158a2323de11bed2f02f04fe481e16bdf50f9186e0344fc510b4e3b075c8a205a722292a1a798b4cd432202c1cd9d23f78a519e95357476cb7ab4cc0a8818d25ba1a58ef1c01882c5cafcaee589aa890ede905360a13704827d86d028773d228b14cb5def068cd2bf66a71fb2e3c892b486c21eb0eab6e6a763d4a620771d99b41553b33b209369b01e3d76a6f558a5c35d334927a3a94904489757cd7dd4b1b0b1556e6e47778b5b41e6ad382b0fc6c3d5d0ac6a4a294ab4b5975d30f92f8bc2b86893b57da5d0c3d42d266a875264804707e2a5c5ce27eeea7c97b68d15510fcb99294d48eeb1132b8e06ed1bcff987ae5d5ae0658520f83cfcc91a039199ad48a7ae4af9f3fe11ddbd8c078ce5b4b23f07af35ef47297be3678b51439d0f4cb50c3f18b977cb98f04b7453c60cb2f7c488cf813a3d7e08644f0db8adfd3e87a160edd62ce5198565d38cbf1de656119a69b485fbd413bfe453e5dbcc08e4c889d3a9464a5460323fe25b2f550d2badc0f0982c6d85973aa11fd6ec44ca65a7f608dd61de09f2dc5bcb92c65b6bab149b476da38b438c5ede4b73deaec433a15de2c54fc421114216448d8f00170ad1fe365c56f57198abca44d184d4022c227d1ecae7172f984e8cf1017dfd4e4c27be1ce96c0d12b23bf1c8cab6f87d4cff74965b451162a9a2751e3662b962529fbe8e91209c1bb31a5f42dd429f70e31f5f5c77be6d2163d05a8d7b7cfb57d94daa47312de81ca1dc5fd1f7cbbb1e84899e735c524d9add72e5b4e8c451c94a67bed192ac731fd9fdfea458a7e30884e26914f96682b01735b113747d2c6b17480374f7dfaeb700804a0e8f176140ef6c20a1cc3abf74f0e41f9513c37d3c9c89541fdc022bab45537708184deee6950afd9a0bcb90b92eb387fa4850df1c9c87bac92a708936386aeedf436e5d7552404d3a48602d50cd0d1d303b845cfde3175cd85f4090871e4ab4b8ecbfb557455221ea5dc1c7cbd3c1a94026b9945137a15a7569f3b3d0c0d01cf507c3f5e769506b9453a12ed2f4fb64d572dcddc8d10f6966dae136b5e1600d2ca0071e031ea7f69c1d021d404ab08829485c834b97443ec4925a9ce69a38c24912058dd6799b82f8fe1cc28ca816c937fe93846ebaaf693da10b9d89ab520b5c1892898b65821fe2d3e44618c83f8cdd55a4711fe7eafc1307ff6b01845160c120d2574185c232fc1c1c3c0ec228aabab7417a561b2646fb29dd506f83d6e349f169cbf13f6f7e05d805ac6688b35454ac0ac0fee7b5fc9bf40499f51bcde1b8e6786fdd80fc6c4008c87152bf76486e2f3a2b2314b22fde68ddb7cb9994b72985042225811a1f48b1192f21852b7176c8cd6f7d57e70a4ad7215632df1f8e0dd9af0f915f5c7fa3e428d5745e1292d31969203703379989233e8b44f542c73ffc18ba9343ef0cd6283e6aeadb215d20688e77aa01c3f4024482214c24a81ecb503cc57aa10e94851cb5f605be62cf8c510f170e81a88a40264c9d283f528566b264a655196122de27f8b80ceb8f1c399b09143674bc21a047c83666ef475dbb99e7af24bf1cc3f38e242b00b6622c81e2a368cd35ecc14d81937464ad6804750e30fbdddc107488f63fa53802bde833b140ae65d7775dfd7ff9d13e88c0c67b5695cd0c89820eeb94ca69f825c41761394017ae7b37203250cea8892afe953904908c841b84fb8146ded12692ea7de1f7b482a6c2cadc0ee1fe9693d1cdd011471d64fa3a01dec690498bb1d6b882b5e5a5ef6dc089bd504dc3e2474ce9c43bb8bb4a8b8cfe1b1707162469d2657d388f33100e63fbc27772a38acdbbba23a79a5bd51d779e890164e8f42456b2759f80a2ce8963feae6e25082ac11b9a70ca384c20132475ad01139460cb482b0dcf00ecdb6dc38387feccc7179fb48984d1681df67a05a2b3214148a811f31841f3be6b1e03a2ce232df3f0fe402858514663f0b08980f6c80d278ab9253099c6f60dd585b7c01d73a718b6e06bdb2cf83b18e7312d0fe1ad84094042f807e354dbab388ee5bb603700ff196e45bc3d72333574df5b1688292e2162b625ebe6204c53cdbf593a304883ca2c1b911a08ceb3da638ec42c54555811b4b515609ba5a5fdf11c5a55f511606da9f1b4d58cc2df1b0c246844478da87dab912953840ab435b2b89cc8924d2252762a083b16548269e2b45dfb959a2ddce16d3fe02d2dcc61d8ba328c466bc06473ea631fbeca4e64fbccee17ac3c80422b5a7a93f829b2caf1b50315b5aca495a4f059ba2e7185682275bb1b35f7e0884236a4d8e7aa94747fa542dc7323550ee74bbc7cde425c218cb37d20335014820823909f476295e41f30b781fc7cf29d597a6d4ef53efcc8ebe2e7ec4c5363a1a1b9d330705bc0ade260a47ac6d2b2fbb9ad83cff922681b3b2edfa5b3601142c4b35536fe33367161a1e318e86f9fc51236e575ed718addb05695f9cf1b00d1c078a38155225673b0d7aab689b3c46d3ccdadfa91ec2b1af3344e43b3817f392c3411319e6c660ffd65899d6d615f863ccd697452a5be32a47821230dca706aecb6cd859a068e22ae2f83fb957275997aec63ee0d1f12a7d0bbc41057854b8689fc85efd24a91e32fcf8a0ee5fb20df3be03ba9f9ec1fe24047d02caceef800c008e65a46d35a91712b7f4c25734f11a56dd632f90cd2c7b6ebd74c4ae0473a8841520dcc88c27f6c2324808257a76ad04bc79255e7ec770d122786598f8f2da41f68a9c86daa85b033bdf306fff426e287b55fad3b0310f96fb864994918bc900c6c4e5699146fb8a0484694085c0259fbab23f191a60e9a45101558970fa41c3669f12ad1ea8e8eb1cf50391862029dc39eab31d9e8cc035f4fed0fd1d88b9f32ecf8b4ec4618930d8f3242b2d87c6d458dc51ac8f321a006935f3dfe901194f8ad4b0a81c05e66a92aa4ad7cb76cea36e5a4857221fb9c157045cb872355963edc4888396fe718dba0ac1226bccf59bb1c9f7e6fe2c68b7bcfb27970cd1749238448e421c97e9e27ba2161fabb2042ad491f9a735df909d815858cb50ff9efd433fbf6019e9488b7730162a7365dbddd6c6d20b1785a192caee2e658974f8ec1e1d9ba7cd056f6a29c06aef24f7e913158394082858eddd6515602764af8feab436bd8a8341d6018f71d6d30802047323c3a551b079c129dfae36a3e54c736565e9045e95ee34e33aa7119acabfb902cce0f1f3f1314e39c2a74543f83f66750ecfae7087a6f196247181e3d41d06ac1763c0b8511a4f6933fa1a75c96bda06607b4a59c65ad4440b902fe4828b69513398efcdaccd6f8c7d26f78fa4c7c62772b629d7fe067e1a33b9cc2fa50eb4bebb1a0ee3d977e861ce8159b12681be23e6c9b3a58dd57b76fe3127ef7fb56b7779f0ec9c766833917c35872d2c581a38b3291237e0f00d75c5ce012f64392dc8d784eb12200f602ed5b0e54a1be8b5a0da958453a467fcdde958e3a2519bc19895b0e46eb58f4f55698bee96e243ff6268a453615138b1c2b2a5ed7f603a64f6e3da0263e5547c4749c2ca7b63d37c7717542c1c9339956f7ae81f24a94483c8dbb3158ce86c2b9b400934941d3073bc3a843a1f09cd85d57d7146694d4fb87781bd72d6523b7015fbcc52038e0d902e9192d77dbe4600510a01baea65631614d905b56aa3cc609228bfe20d8a2ee3d60db276cbf838a7db9d28da9eb939dfbff79f98b46818f379480d2427d74eed7bd78c3ae89e4e9d083fcaaa3596d69870407215d0c6ce15cdc265e07b6190a35410de11129815004454a9e89fc645e07876911e7d8b24a9ab8ce55daa4bbf4f0b08c1821d4862ad7205e7a5a6f569b410bc8c4b965b85a8ec5d0ab53a98456e240f9238e898981023e039cf73820d53db879ef7beec1e7c7dd23bb30ca1eacff1582219f951a89f46ba8df472a1d02b3ff5fa33c9c348ff1cfc3e258305c99dcba96ad28623f860535c53ef77328d8903c7a0eb41429d2148d0a295d8a521ea408c65b1898b69e947f69c87f14131a2d6541434b14e46fabe8683d054a33108f8d4fc828e53851da66679990013fd7541470e4bb5269a6dcfc26afa8f479cdade8893638f22a938749e0c73ddd9c1518d5d5c14ab105550f9ccee0f6821c5cda278f21a222c33a2a972b24bc7434cc143e97ca7d0edb430a2151cf27650e70e988263c945cfc7d13c0d33bb90b5e7f1c4e90d26a5efc184448f65cdbbb248063eaeec994a1ed588ae9c8ea580cd22791787aaf937ae3c6d49d2aeff406c3a6a77ae1e6fe26d961fbec863c50f53a839ae23948a214890800c93ffe2a1b05bd7b0c0e2e7b18b535315ed0c34f83d05231f3c126748c9634acafa66518e6a780c369ed4da38eedb2b33b8ffaba619e76230ed29e62c6d21e9c2f736a9f0ccb70215ca90e14bbfb1dde2f7518c6f1825039a0daeac027442790f8fb702722493ecf66f146634ef9d9e50ff1bc8b45eef50c7ba6661f0a9cfe039f25ce1705546a9817e331073b4bd1d8d7a83c49e25390205a647e2a01ec9bb747d597ce3990ae3de38c44191347f4a31aa96f32bca739a4760d6d86cf4b7b39fa6a2c90a4779f2aa3160bfc445d2ee8724c1d1201a42728f8bdf1621de056b86e9161cc6913a7cda61c8769ca34f131e51f280d68c9f5e323cfd926d11ea52b244fb5b1379ef7404e2846d3426558dd364f4b638d18864b6cfc13b3321a6a2c5353a0cdd9da2da255c16c537c9c29200a5d1e30388138297412dc1202bda4210a4f0cb15df2407fbacbd1253792da7e4ac4f7a6dd8bded502cdb1098a5f3d0250066da49d6b4f42fc469728c94e089ccd9858d3355cbb0640c0102825db9f98bd8a47500dbd506e45dc8c06a4c2b766965f1c22f8876fd8da38056d703534446bc0c68ac7539f8c0b5392eb3f460d4a6eaeb275352bc5a25acd3b6900df52b8a4544d3c5fa8c236f6626aea81db89d8a9365fecb39e1afea8ae4d29a9d866433c64776da764f66f3e92b8c2019330ffa83f44977625aa704ae2a92752becb498af1d6df625ee6108873b20fa378db86674e1ad170fe68841a66350d45d30cc54a56267d322c15cf302da8124424c2ab9566916ed1cd1cda043606df1f7b961250a46f944b163ca43d076595f022ad2dd0b4b7be7bb192a031337157a7ab295c15591714035798ba27601009e49c290bc2939e5370078eaff1438242bcc85212ac4a77e700f6710993fa5b65d7c02c1eb586aab3962f783a0720fb3a74c46f317c81581646d5a577cda21f758c988e742c1c4eb31858f8714c1831e4b72a38abda0311c8a74e4a7fc9722bb1825b3d42b31c1b29cd310326c7551f000c86ce7ba6db4f8a4a254d5913a7a3cee7ab115095fe86572fc8c779c7645c14565306ff36e650976a24a0dc28b7ecacaaacfd23dab25049cb1f08c715b81b80e6bb2c5060272842924208a13cf2ab3d61519da2901cda0b8ffdc0b8d42d2c707f14d9fb3a5dd79583a80693589bd8ac2cfbedcc291e174802020986c6400612155982c1285a2db95d39f5529c9ebe41eac4fc2afde7d430f9c27301e9f30d5c80975a7ab55cb5dd26e66b32056fedaf18e6ed7bcabcb4acdaa753413b18a50d7ada08ab84dd57cd24cb0e4252ad35107c94591820e6501c49db23ff3057ec424db07f1f5728bb5ea5f03a1c9e71737147db91342db1bd7ff337be6eb942b6f26456ae1f56a1c7b6d47272c55326fa4cde1a8107d564f9a0b3b6bd572798ddd84aa2f748c93d701e4359cb03a887934899e3eda44e4744722ca4da978414e478fa27489d5cf1a4ec50793992e75c0ad2b8ca0a3c88a52417dee44588d8634ad37060533a3005cf1db2752ef697dc76d81a953baf1cd3d7056abafb2a84879764fa559fd3e52407dc907759ff6255cf4ba3ea71d2c437f325277df70faef756ac9cb86b03af39ea96834f113114b568ad840982faf4761f7d1a5e402eee33010157460e71dbdebfdfd8825614d35e7862e8004709e7a85d65f25f9ad5e34a8472727c2fc1e095a13c2e9a7cd6650b81ccfb8516a840f508b43b3936e1656efd343298061329c2622e56917deeb030d114013a74f85678bfb9f520a3438597610eec49dc7ab04fbacd0ba33495b6c8529bb2f0e25fb5fb79cb1c8cec218aa9c52a59a1f056fed507a8b33fd257d10a0cde36ef9487f59a5d8051bca1e8ca63a47ac7a4710c140cbd5703eba6c216ed8f93a8dd16a4aa6fe0575dca106dd69b79663ed01c7d78dbdd71df06a5e0bee8613eb2568e46563995e6392842a84dd06324596e80b6d8acab744c5861eea5463d6c62d05da24ff07124e6454cd8b67c9a89f944bba7b1d1ffb2972c473b25180048240f7707ba0b97dd70f9f31d84645628f0ae813fc77bd330c5fa452212b2b774a333e812f4b18fe6801f7151c786a6975ebf5974bc745902531d07641e8128e3f23bf91d073bbed6c2ffbb45afe022ae55583936738294865da0bddaada6494a1fee7266755421afa9f27518a27066232e7e50a8ff057dceb212ae12b2077b6e554cc22502445f7b99aee2302a0e3924f643958f78e3fd8f47d6bffaea7fc40e61c3e0bdacde1fcb744aa1e1c38b2f6ff200c9d4872400f6356132038219ed4734c958aec4e0d80c0c1fc05676ee305de7941de5d0756fa2ec188260af293366d19cdaef3752a258e39641224e68f600b16c2b4f09063becfba79e1b78a6ea229336deefd10c7896ae4657db6c409b4b914aded0e3d30b9ad0bc72b47512275799bab0e1247ea6aa3a0b7b321ed1ec169740585768c88dc5d62e344b05eb57b0b8f5ccaa157f8dd3a0bef30204a4ea7d22f38044a48e546be37ef888ec56e48ee856eee845e66e46a1f51975eef171b88327ce13314146d020d9ccb9e7a2a64886d772300a265d3909de34c6fb93654379cb951a44428cad208d351977927b90fbf0199ce16fb4ba4080e01ab194fe04526e6ae8f47adfa7f4be6a79eba7a8d5995fd16783f2d5f468190036e0ca12cd4767cb8daa0711928bc3a8854d21a9e988d993670754512eaf4a634222dc8e2115798a12d50792b8d7943ea9becae311fd85a8b391e454251968090f0726e871344ee682749a2011f119434145e1261e0dc2aa53556e6c892218c6c0b74a679aa19b879b149d56b3babc34c97ef53786152ebfd007878accee37e9bc06f43dfede84e37d8089329eb74e2176feadb7018b495baab94e3501401b983029599293c25974c5be4a528e584e010929818230b42fc4cd309aa4a16f6d23a0c20fea0494f144e133d4f3cba6cb127c8388f26b413a211cc3979e239d870ec84e3dc0ffe3e015844c84e9f77d726c7b13354afeeccccb33734e1f5c38f891537b7a8318887781f773de5ddf59b25b0d501ff186d7fbbedd87075424313a32252c3a1a2d51657638306a9a38741d46a0f407b789406efb8b67aca7a93011ca05d2084260c0878023c190036d4870af90b166ff4196a00556021ff86618d640ba5a72c3ac9520d4ad67f053ccd876890b01e2b58a9108e06057297067e768c36b0b21864b3f5531a98fefeff6f50db1ffe01dfd860db9e946e6276ee422373375052fe058f03cd7a9f87bd0bfec6bd8c4accdcad71e89857282f42c3567166aceae70215d59835c5bfbef7f93509f4ea2ab1160232e8d1c4308059366c34b53c72e2ee5b5b49f3f50dfbe28284efd1ab213587a63a7272c4113b379cf729a6a65b67638debc8ea1bbe0925711afbeec32a3b999513960151084c6040ae2329ec31a7545363742e955cb21580f5fcc2b49e416d587ec9b6cbc486e484155328eed39ce1790ebe7b032882b3f04e308be7c3b5a34a3be501ead7ea3c066c3cc0817873d9a7a5d862250d185671ea03e68be7109c965f4e5d82d31f75a0a961e18675a3b39130e7f12178b28b54a0cdb6a8960577e101d8a3e433fcbe5db1c39cfaafe23f419ab049829250a7b002579e3d3642442052bf2b6357e8c099da60a476a4c43b5d65849d980c895320dc871bc32a5a8a5fe4e254b85984803cbfcc8ecb7a100c4125223aa713e654b80a756abc29041675356c0cc0c70f6dcba11381aefa2c71a9a6652e120ae9432b89af335c7f82463f5d5781844478f05f47e279e446cf985b93672881c99bab05811cde4d0c7d52d0d6242633cdb32927564e2b5af5a5a7874518a275b12d34704c9f0107397bede671e944ef7bb5aedf232868b34161daae22ed05fe566808324940e93d10faf2e033ae0bb83aa0aae7099cdb0fbb40bbec2ac6c86292a2a135a969aa0177d7e719d7e866b0178ef2b5d9a8537f3bd9dab1660b7a1929044538c88bf6418464beb553e2e04bc937e859dffbc6af6d8d25ac50cd2dd76695077f3b15bd02cc1988b420b4c58a9545c6454749141e88819f40644c3aab54bb988c6b5909dec3068dc9e2610c66d144a43cbc61d5ea8d1036c0ec0dbaca117eac6f22f05118c389e6bdc562b36b62e3501e2b5dd1c88b0d993c6eebe23ce983dd4abf47f25e1dcf0d950ce19df0b85cadda41c427208045d2bff1a9ba06f4a607d822d582fa6bba1d549bdfdb5da0dfcf653a1c5a09cc930146b57bf01ba0d1c637c5e2eb92734b030de00c1803cc1deaf6c4822966207d0d1190bf07ea8d48508e23f34ce8d431a9d65346416289ce62c5690e8fd7183120289a821e61e4c563ffbd54434e2f54079f19447c3c1be943ffa00a4441a6e088a966b03f89c597d1b0f119d23c402583e0a0fc6cfcf983418e9b8e5a3e8cc5d8b7d556bf2cf9aad95caae5ba81ca308c10da7ee2d739093050ae4706061ff495297c01f13295f51fa70e1e3dc912a8f40e15060b87b9875de3d813831330c4a84278bbfe2ddbd985ef5586c59fb4aac171dddfd90f929ec73c86e000cb4c85dc99651dc23700ecdcaf4617938c8881b9f5ad6d17678b42e5367a9d95eb76907632598b36a23443052cde884f1e7a4d528c211e0870ad1f72bbc4ad72faafc8d102842f340402ef946f4ce172c34c986a9b6dc908858086ab5862046d27f526d0acaf184520173bb016c9c0235d524e4a5613bc3d922258571a2c3b6896b89e2439c3d46f78715ccdea040ada11ece0af0af4a4e4a90cdb33ed3ebbb43bfa2f1b9d9a44078804637633ee68c74e62b2e658c1a3f53e766ce56eda15cd9207a050584457cf4b47624fd0ff80bc50caf3102bd00023bc1179f8b0b34bf186e4e572afd42f810f6722b1223f87be7d030ce405302d5c6d605442b45397c07667128723198e5378f04c93051de504e871122a6e320b26a1c7226251f19185176b1de604a1a63d0d72eb5323f3d4efdf7275ee588fe1d086cffc837cae5e94d59d18c54c8bd11694147d2a7d99d83cf0ce96aff79edcb853ed8f50b665a4a28ab1912a60459408da0bba0f0ded5fbe14159fd88209894806bbc3509a808b2d6bdc08b58a4cfef490d0dba1aec4e663be0e29c621b0c0f57985a43f8738626e426ee8f144bfd47b7583b7583e0f659f1aef4ae3a19f2bea1ff2c7accb72d5a9718cce4ffc924b659ad3c663a8aedfbf98445b17cf2e422f426972ae5640a018d54c8b4e73c49b8a91174790b2c5caa6e920675c91381720f72caafcd72951aadac213defaaf6cb9990527eb61614b8fff4cb978571a89c7acb78c5839e14cc03612c5857b89071a29e660f9ad27e498e64a6647c7b90dfec059c6b9c0125c5634ddafac6683f061a8a2bed29a428762f39d32a3d291eba2a33031cc82673fb2d4024606a6314e9541eaa4542ed5fc1056d3c862fcab7ef6d0882e0898602050b73c4e0d800d70c3fad771c2b6a408fdf2585a0d13c36de25327d98d38238c21e33ab71e5fef9c4d71107864631484a89094f20f5864e9b12e2d03e0431fa553d5f9197ad70549f8201c484684949dba7907cb9ab48fad87a1131d446928f2dfa1e17cb30e0e9772b9c3dcec1cc6de9ea63dcd8f9c1f4de5fa95bbe5a9802adf26c245ad44747ac9f2df322498078c80e670770cb68b796a85f0fbdf6290b0a85147ebb703b83a192328a65b30d7bfc4ee8ba4f1a13746ba40ada1591430696783d9d9d060b0d3bc7e54249228910692011647848333b86dcbac755e3ecc5fd026cfdd5e5b9eef66dce99096cf7879d064f446cbb87e17c84bea6863abe02b774a8430d81980898899507b1931f4773b537b128d8dca91dfbda1ab7616382e7079f7fb9e50cb2f38f548fe2d5cdc42b64b75917d0242641ea829741b22b6891cc6e56d0de01914951ec1612757d2c4178ff5f321efa983deda6fb851080d12128fa53d54aec1abfca2168106d17e38ca1d5508b48f4eebd6b00ca897af36b0be102871659d1c803579cb3d5222e635eeb3ecbd2c1068c934650830574c6f8c3900671a6344d4d605da8e9f693ee85f86ab3a9c257088942dd4fe504b8179ce741ab928770eb327f3c2d29af6c1a189bb41d74409475650c7975047441777e0d96868458ebc92ac65a9a884668f8de038ab4e34a473fe30a68e10de68512e4cda90f4fb86c83a5ee2a61bb1b4c0852d91fdb62ead420162a916259a34bae2cf28796d5ab2dd3208b2329aaaf68191258789d93f12797510a279aa1d7a33b0c9cdd40445583ba3aebfbc70e53568e90742d621154440d4a8154637237204a900a1dd03a56fae0c9886d6f15c96dcc0a4328d4ddfffdc9607aa551c8956d2dbe89e62d98516a46781740a95b9c724d93a2a7b42bb9133fe832c9a0c3b56f0c5fe0f091f694dcb20f8392ac12bb1c38d04fb7960aaf998c6104d1a518e3153f84c3dd4796d02b7df9787f7f5492e4e9da688d8000241b054fadafa7e522ccb30a627dc49318346fbf8827f7b6d41fbc3d3b1d1310603d7e1a381d667f2340018c0dfb53f49f037dc3a341c73af8720d9c1832d64c34bea85504e07fa04cd12862288126eab349f029cad1a54645f1eb6f8348dbd7fc919d7259203c2010cd799634a96fe6d94ebca5e3d1a1f2abeb2c1ec371cfe9168f12370a4750c46e2395b33d8c4701e050027101dabe768bab8c0029aba52f0f66d013e76e234ac047687640444272afa0415407d19382d4ce83ca4e149aa340363168cba0263494024a02466d5f4092595507559bcffa3810bf85f6c18383ba8aaf50b72bc890878178960dea2ac8fcb014c05a89a7f86c794bb61a3cc0c15d54067786b027cea8e5eed5fee6be27586dafb3bb4f466f030547ed8f2d25b663169d5967fe76fbdfff524a2953f6060707af062bd6b6c1020b20205b3608ccceb23d809758a2b30082b0871bb6e44d984478132a4378132bb0246f82b4ef8609e14d94fb6ed8116f2205f82630923761c49b5015f126566f8275e44d04c1c89b08df84b887fa8dd934d8110a10b3ef86e136ec16ad0cc76531642f90d120ab410603590c4c2eb3e1c2789abb575cd602990b64333ccd95dd9e46062e630197bd781a93cb6ce0395b2cc7b55578ce96428e6b9748ae8df34ee1250f81d9f399739db219321cfaddb29e6b73b7a8ed3e7165b86c867fc96cf8cf81e1a21c1b031a7110901d48bf7bc50e1fbc3b8bf578f4ee588c500649c3bcc1a37d909d8de4356bcd0a1f5cac48ef2cf6c2a32a6888f1a8c0dd59ac8547797c4db05f7c5383c44e12354a76d63bab39e2d1bb6d1d0464176d9ad82d06648a1fbe892d913790939fc035b4986e377fe1ff321a3af25810bf6c781327bbe19b82cc0559b0df2d4660c49c24019e27308a28ead222ad572d3d12b95c3226143b3c1f2febbcc353b1c3376416e9989856ab05c3c7153d138a154150a8d8dd481a4c0deaf5bb614176fdaa4230e09d29b9331a075250b1db3b3a2b2854106083420000208bd59c60819148c364fc72f920d4d5b5aa8f1417d8920846b991e202f296f7de4b7e49846c0159a67425914d963e7e9718731442e43d5b6e99089be5627a2155a832d5133ca19cecd7bdd046698fbbc5ce6921ad542a954af553a95c8654d9260327838b177c403d74dca8a1f0f5696c2e43bf755df7c86d824f337331b3cd6af8d5e4a25709505d718e47f5c2a4df6d8384afb9bb8597f86e11cb69d92f47081949727b9aab83b7bca8ce98ba7361ed7ee30be77969c1ecb387d487b1f772e155bbbdc024aa377ae178ac880f00929719799b9733a6de7a407d40d3b187d70bf77aa81e8d1eda437b2f1c5fa1f008d3830102e36bc5d0b2b5702d1acc15951492d8ea810102d383f1b56268d95a34b470268c6f05c6a702e34b81f191607c228cafd5531a55b504a2c6b1b45bb84a5085c94a6d85c70a6fa5b702649be48aef372a5598a804576a2b3c56782b40567a2c2b3573a5b6b2525359a9a5acd4566abe0e2c4bd4f8537fc01f6e051e61ddc633a6aa2570456ae136f216798e35a276a8025449523afd544b5440955525891a749b2eab906a03c42f21f5696c8ca82429a202bc339b2147fadda59edbe9a75a329e7ef8f4339d7ee4e977fa9d7e479ec688d045551555a24a608e6f91e7ce5de23915e7b1518e1bef16be2611e46197faa09242128328bdf5e4a722bfd82f099b2227eb9dd90c39719b1e1b5b10dfd8c8e001b7c3373632f860e322bd339b17822927c11e27c1e08d208d2089d2eb2b94d3e6f6537fc01f4e1f3172b3d5624d8217bd2b29549e317555cf1a7005962a91e7f81e79ee2cb59ceb4ad64e9ee2273fe0af6745e26b70b29bcc26e391bd90ebd3c48031a124568b13337264557f2a10f81b4f9b1f114e562c156b9fb88dee69704fc3a2d11343864761f45f10500f9b0a54f198ba3a396d6e2a8b9563ed12cfa156dcb6735a48422420290989b4967628e94abcd20eb1446e1a594a5bb8cbd07ddd316543559922099192908024a15e3b21ada51d4a3b4a3c91b492564c5a4da4955c512bae949e4e9bdbaaaebfa1f5b7aa5c3a36ad2c1e0b9654bb650554ad56fc05e65c5ba32a6654f1cf59893ca7daa3aa65a53a592e432aa0baae405508a804f853b158ab75fda9424025bf150a97a11ac115c5e9a703921653d7f5a70a0195fc60a36a665489259a51a51af1705cf1cf6919459e5bedd20ad8692e2b12527de9e11b598affc547ae78885feb10505d57a02a045402fcad4340755d81aa105009f0b70efdd475fda9424025bf75e8a7aeeb4f15022af99dac2e43173dfdd475fda9424025bf7024d22afe392d37f34a808794ae9695cc72128fea177e96d78b5e98bd392a3cf76f3d83e764afa8ecdf5a86fed1391409fd91b81d11fc5a1f12f4373978a379f8d7efdce36fb40fb987fe3bebf819b94b8dda20d15c9dc786b85bec1cd6fe1df338a49fe9658f6fedbd39e7dcf5c98742872922151725f765429173d96112362d344142aa0367da6298bd0824a5c88554baa369149231189e83d99adb009fc6a6894dd066b55172d15be4e753718ec765b70873ce24fd6e9b23bee6c298addd2296c3dae710214144fab5a93dcd6db1cedf28c7252609248da68773cc4bfbaed3f4f45b048982d26a9d9ce7dc2dc21c1e9e163bc7b55bc47264760caa03ba437190501eaa04babe6a2f162246b1272f1b4d0d19d87d2e1e0be20370b70c8f09f1b2bb63782ce86dee0e61786cc52f9d739e31dfcbee76019e501d501ce88e84ea80f25025d0f5c5e25513511ecadb28ef04e58d50decb4653433e696ac03a12bf4e221d0a0a46c1a42dc2a4303ec0f45a2eb46aad5beb85aa85c54c31b570a8ae345a81f1012685e9b55c68d5f08d4d0bad174d5ab73b430109698d0ef4c6a661ca206304638a9f0a1854d854bc50715361830a9c0a9d8a1a8fde1d862469f21d1e7ec1f0b39d246fbbb31da08a172a6e36fccd8e12ffc2373b2b7ea9d0a9a8c1337777dd7ba261c0a5eff05c43b66979ecf6343b4976802b56ec2871ba321a03172e56dc5ec692af10698961b9b609b0ede23c2ed4452628c3c45573e170ed5c3b5c3cd70faed4e57301f1e8dd6148922ad72f2623b4a11c0643f2b2bb5d38ccc8dbdc3daae170afac4bb60a65645c3d57cd55d3e172b95c2e97cbe572b95ca31399d60a5d900e0683cdccd0d09cb7f3c5893b819cbd1388d3771271fe4ee09964e55c49bb54ee94f3c5793b71270e9fb895137702397b2710a7ef24e2fc9d494ee049c265ca8c0945cb9e49893a34ab2555122153645a2c1dab066bc73ac252594958409612acd5a5e6c28254b9d8c290244d736f140dfba585b857777ad0fbeef07c3c2db26ab074ac9d8ab5636d9a49aa9cac1deb084b65256101594ab05617162e35171bcc08254b0291c47e415b75d26240e6e9a4c57a904a2db6eecb6ed139da39eb1362f609261572b7fb04176e234ce12cd894d2c55314b92b2d9b25c54f09407e7a74045049294977056df3b6b3c28e3c0d8bd0455b2c4c150269adcc8422d72aa53cf1c43db5b28c995ed307b3673231832c2c586a2c31b0d858702c343c7a771892244bcfee8601c258883b62e46b84801755b2c282176d72d19a6da73e684590fb4abd206930d5aa3b1f84ccf4ce6246cc1ebe89ddf8994b82cc1e90d9eb61f674983d5dcfd3d89898c13b8bd1606131a3d7ee2c26e3d118709dc5c642030b6eb3f4c08c20b30e05958899bbaccd1dbe69914288c9ec149e6be556ca57b2286b250ca5ad9ca1bc953de5cfa37787a1aa2c753ca0917ec370a6a75f0e3b62063773f2191b7ecdf0ccd46041d8106c8529e130e0d324e91766e461422398fa342d90295f79d25c29272d3664c782fa0dcf944f3c53be9245592b61286de50ce5adec297fd05206884aa5a974b753fa2447ccc71c1bf235363391d1bb51430c1832d880c82738987aaec005e1fa155378eed4bf4ed3b533bb68305508393383c258122163b3d9b837692525410a966a9474251ca55d89c7bf4a3c5ceadd1290922faf768ade3636e46b7e4f5344bda81027d27d4fd3434a42485fef2c16f42829886f6232fe15ebb93a8d145405633afadd61500ce262b560a9460dfdce62311ee511e3e19b189026bd06098fde6d83a94860457c8d2a24de8c27dc656f1787c1945c740de2d77579934e4d1f9d30a08b08326f1aac3c82ccfb84a49254abb63869e61b140470636214b928869bf865636208300ec0249e2b7ab231665281d4f5995d3a0b6fed8b5a9be22c5fa3ea76a7002285cf0023e6345d3c31623ed355dc3e8dcb558a98c3ba8ae78c989bf003e8879e143b52f0643f807ee8e977a7e0a5d8a1c3d3d1a163039d1d3a3de8dc4007073a3c7472e8d040a7063a3ce8ec746ee8cc4007870e0f1d1e7ef8f8f1a3dfad93eaf0a0c303003a36e8e07478e8f074763f723f0e3062feea327a04755ee8dc623270c17eb7ce4de745b7f5177a0a3a0cdd4557415f418fa1f37414f416dd855eeb2df41374163d86eec2f780f13d5cd0efeeb7ee428f417662c909608fa1db7aed532552200270901840efc491132ada6930b5d7e0203100f5c4119c1fce109c1fe010c1298203049c217088c0118213048e0f7084c0f1e104e1f40007081c227084c011c231a2df8da3e2088143440c87070e0f87089c1f8e0fe7071c14630acfc9d8ad673d62bca7c1c1f138bb1bd3f1afbb55578f0d890501f5dba3dfbdd3c5703872f4d88d1d3af4188defe9b1181f93e161f4bb676c329ac394f03577b7384cc52b2c086bf23477bff0991a6c897ef709a8a0de8a1ebe49f1c3af4b9bf5c0dde0fadd38bb3bdbe15d1c1c3fdbd9f1688ae762604bfe756148fe75f7ce8e9fc18e3c7a673075651a514e2c96164b8a272994cee24c37a331cbe1696635663acc6cc860b6c39dcd98dd30c361967b9a3bebd17c2663e633fb59f1d90ee3cb36bd6c94d3996be121083b0b0f01d9ed66f19773969be598d5a0df3ddbe562760bcfbdeccc73b6302766b366b97fddcd32dbe15f778b6232f4bb6141980a03c26a421f6f16036fc45d331b96edf07ba6c32cb39e74364317c04c05154c28f4d6615570e9a41012b5599b0653595c7cc5ffa227fbe48f5fa9adf96f2e8eb88a1d8e9ffd5ff269ec09db1ac5521cc5b90a620246cccfbec288f9ee6289e762ce14510123e62f2e978ef91a1477e9342615483dbb7491866666660666b3d579012303c673a7f09c6b93ce18fed875923b6778c2093422dd80f18413685604e1adf0ade8cda0f41e008111f3544fe1399916614e6bb7b60bb7ed1c9915bc8b66263f5bb1a3866f54ecf00bc7cf56f8b022ddf1331534a8c05dfdc3dfecf0d8e1ed68207eb613862469ea9aaee95affa09efec57a2a0b8f09e131df4583a42c8c766a6bce820c8721812d61f14b2cb1c4123c2d768e2a0739e79208c6ff4bb1216611fecbf545b3499b4626513489a6d16432654e92999b287f6242452251d6b91c738a8acaca8a5d491953a69531c564a698a659a6504cf384344d13659e4ca6699674f9e671881209bf177dfb38b0bcc71985d622d168148624520d8cf11645711c4d18e31316618cf188370a6f144642e24d9a5098c1262727229108052535a29872ce5a8435c61ae3aba26255b4de755c96292356c1ae52aaed384414499cb1c61a6bac31c65a63adc53c662d128db4168d3aa5c37b348eca9234d5300c47dd6a656e20096ff175e4688bf78e5a6bad75f8eb78639cd2e11209a9353691a612a506a4cefb44629c31499ee08c31ce592412894438637cde7df9b374107f73fb82a485fdce5a77a34029afdfcc83b0c744e0eb768f46a49d160219c1b34046dce87693aea0048aa8d11f08c950a791fd8bf8e90f8444a8d3ccfe4886f493fe40487a19d8c16f074276e063073b9ea603367c03693713f22ffaa3c6ae3bc0478d5a905e901e803c15208247440e226610f1826e77d8015f105faf3f908fe7a381af4610a0a05eb77b0ce205c9a0db957e7d1ce871034a8fd01fa847adff0efb03a549baa83f50eaeb9608dc04e4dda28b71680625ee0ecf0842ce41c8e61c821079a0453c30e540a47570ca3112f90873e841f340ca4239e840e650430e3066bc1073e00967f08c3f489842209846d4d07844b8a2518456ed2669f868f8e879861f248d1cb99cc1098d19399430f5732b730a8a1152c61f3456b092aed0b095846029eda025ab74c1bcf2c161a503ea8af0744172cfee21d38ed08554c24b8e1f328cd8ba19ba1a2c4e0783e43925cf50c7736be586c2989c90c9e7b96d811467901949be71886b24f9524540c7d1c748f6ff91cb11e666e4ecceb9c8b53055d61007551462c320137cfc88c1929ceef206b09de843ea7ee050439fc16e1c5c8cfaffc08167e8c70dc09f4ff7e386f4c70dbc1ece409a7e4ea8b15bd7edb96ac1103086b8f5071aa206042574dd1f0808c16ecf0688aea118e40305c1fd8182e8fa0305c1f5db1f28480b7aa93f5010186ec075da68f70a43e8d0fb030d31a3932900f980627f205f91106f06e073fcfc0292bcb33c9af68617fd7b73944cc69ef76a24b3486b91de48a05c889077cf68d9c769a67c3216314779202d1a84df625c8e1790b4fcfdfea891d680d4f931c6196392111263bc35296b7ebbde404a7f537680cdfcd4dfd48f10f12be1853e461036e7c78f818f838ff54e880e4218d1389cff451a8f447cf711cff64b247dc32352f8223cca91bf69ba168eb0e8492e885ae71c84ce3908fdff347edffd380731e7c78f310a58c8638c0216f218e39edf57e31d44bbedee4d8cb6ddbaf626c26db77d6ceb96c5a31bb7f0b35bbbe86da21bea5182f35d5ea789dd6ed2db2d8ef8eeda926ef41bdac21a1ff95884610b3b253db74bda85d7a55f1bbe1915b909e194f48c86fa1d35f1d169da054fd72ecaa36db989c5d12fc09097d0efc67f8e7ee3bc7388826d7b408eab7ef78ffdeed535f1af3137193710f96f44db3e8ad7fbf55992244ee1be1e05bdbfc4fbbe5628fc9e417fdffc3bf39ce20c3ff541ce38bc9bc4afdf3dee36414eb8f33d777844396f349653e8272edfb9007ee111c77b1381fc9c70db70c0688b4820327db450a4ca67aeb44dab0d1fd860c70da7700293c0c62f31b2825f2d4774a2f8d50389451e5f0e820dbd45ad335940d2de77025fce81dfb9b3996a057b6fc7fb9eda04f8e61c44b12482b218e36b6168d13e88f088fe9b047c13e17e55e3740d77c6dadfc2e2da3f5af1780c26d9d602ae66cb46fe46d7f02bab6fe284e3d122437ea67717ddf1a89099e65df4871408df452fca0989daaf7fb1122965b7d8392e7ad35c5db57f5f8b538a3128be79d1577401913ac8ce8626155cfac9fe135714e372fa9d889c54d40bd40d45030a87cafd0bd5835fa8dd455138562a5ca2782b825c1d9648272277160b52f14d4c464f3da9374eea4945bd40dd5034a070a81caa078503b543f1fadd237d92a24232a1c8b9443b2123377e3d74a03143062e2643ec859d2db96289634d4fe14b01441892e44a15aa56a158abdd99e6419d7066d5a280a4697a82922a553097e1a933b7653a1911d94f7644a6ca96c880b2a08c890a0035153b15381ebd3bb5c3cd5ad17b415a2dd3c974329d4c27d3a9643a1911d94f7644a6ca96c8803226b220006afdee90a744969165c43ca6a7f0dc19b35b842e1c066c11cb719dfa876f6674c19e15f79bb9a9e4c4bc40d2c2de32c29caed3053cd9a84b54a94c287227e69cef544c48ec172bb2d11cce0210fdee58d0d7f44e3c08508c54c70d1a326c35c115860c2e5ea8d520e15f3543bd068947ef86a92a4ab803e3bf09d4cdd6f0ebb6ee4b35b7ce1bebfe258c7b288930446bc79c8768ad75369d6670c218f300c502838837c63afc92595afbdc3e4ada2f0acf1357f3db4998c746de2d760e0bbb448e0d6c7b0e475d7fb81fefac45e10e47278955ec8d8c7ddd7dbf46b4433e7af15fa22d6345a97f89b6c8025dafc0bbdea2c8bec5a791d681f82d5249255ca224a608e143d2afa853e0a2a21de2589a568925614892b6c68464346d55554352a9440af745c5d5641a4f23a9248ea35822997c6c943dbd80b48fb239b456abd56a2cac90add1ed0f0d2f601095dc7df7dda8552f7a42e404258598acbfc270861b432f89a3c9348ab5d2ae91762ddc281ca0a82d504fc810df90f895535488b0f648911b2ae8e6268b4aeaf66facfa2f247f433af775a1d3ec124ad82561786edf0aea84328d2811552285a89108a55128140a9531eada009c90a9e89a396bb709bafe2e92776653d86c6d4bbfbbbcf6714c5b73bb0844fece1b9f2b1edf47dcc561fee5d1bb4ff6ee9a7f599cd5d7dc7d5723209db57e7e32020ea43501b617cf20ede6c1b5145c7b1379df941c69714d06a2ca0de168edbf45fa77c657e506d26efbffffaf5a21ec777fa05fdf68f7ffffffffffffffffffffffffffffffffffffffffb24d26936994c9d80767d107e7920fce3e38fbe0ec83b30fce3e3c954aa5522e1f9c4b3e38fbe0ec83b30fce3e38fb9ca6699aae8c7d7026f9e0ec83b30fce3e38fbc0b0582c96cbcbcbde9c9f301906065f0b83fae0ec83b30fce3e38fbb438e7270c4cab151323e3ca2e17bed68566ec83b30fce3e38fbc4a03e38fbe0ec2323e342d1d7cb041360337966065f3b8366ec83b38f0bf5415fa318f4fffffffffffffffffffffffffffffffffffffffff3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3f3fff3f3f3f3f3fff3f3f3f3fff3f3f3fff3f383b18ff8ff6fdf06f15b16672dc25a340a499944c2d7924421a9248e791cf1b5e35a1a4d27149949125f4b9ec813945499cb125f5baec1a126c155f3e8da9aaad9fadd293c2efadd2ab61518fd6ef3c6925958f0b52c355e8e1e783b1fe90e03b11056828158c54378c5ea0d07a306dc6d46cfeda277f7faddd7c70dd2efc6bd4ccbd927ca2211be5694d38bde40d2f20d4f2ea2288aa5daedb9babbbbbc9bdede9af67c3f15ef6ad757abd56ab5fb53816b90b4dbed76bb5dad56abd56a2f2727272731e51abcb56bbba53ddf4f4dd3344dd3dbed76bbddb0babb3dbbdd6eb7d3ed78e9ed85384dd334bddd6eb7db6eb7dbed763be506322695da9ef5d99f6a81699aa6e9ed76bbdd6e585d55555583b7766df77671a3344dd334bddd6eb7db0dab2a56b94aa55ab1582e2e2f2f1bc5de425c8fee02699aa6699ade6eb7dbcdf67cf6a702d3344dd3f476bbdd6e38df5e36e7e70903d38ac93131f8da98137ca37b80bfe2605ed775bda5a6c5d91eabb33bcb4bd334cde999539813a6d58a89919171a11945f1b528190b532053b3b75e0045f736cd350492abb96bd696d7d3e7d379cd79c5796db546ab7b74fd6e9919323af4bb5dba8b5ace42464d2aa039fadd2f930ab6eb9d09d90413f0b5268427d50a2412747c6a54378187bcbede835ff846f7fe379eb4b074d248d3f4f9748ff6d174cca9ffcf204dd77767291ac8fdca3445b724c911cea296d538ef3c023b144446622fcefa57a629ba25498e44235c228525bc0221e1688b56c818af400849e402a9a4c5d1f42bd31cc5d32d49522ca146fdf7681461aef97d9a7cda9c45329fe8cff8febf2589a517a2f8d95a6badb5566babadb5d65a6badb5b6248a56dbd174b2daa2acb6d65a6bb515599454aa2cadb6226badb6a5b5565b91b57a8bda8ab44561b51559cb62378bb5d66eabc36a6bb5b5da5acb72b1dacac06a6badcd7777c0daba3d47914d098a1735323960b44567095410b039a26d7344a76dcc196d5bcee111ed16a10372cee86cc1029523da3747444d006f8c39edea641a0e7bc41a3dd6d6b8d97d77d8de7d5f2a32448b7bf67eb7d662bc51829034b38b1d6f9cb3ed9cbdba05d01b6ff3ade8beeceabeb099046937cd1c95525e188a763520d9ffbdfbe39328870474bbcb21c8fb765f6cedbeed10ff667b712e8990b414743a1a9544c890442a954471244f27148a8ca1a0a0a0a452652ec3949c12b64ab234cb5549246fd1debf38b4a33eb3b8b23b268231d952eac8ab92c86a65d2211d41b2582e2e2f2f2614019a79a804cbdad169e9d00800004000e3170000200c0886236196a641107bc80714000e537a525852582c10c882d11cc56110c46010638c21c018620c21862154732700868559b5b3201a75f7b59a2d0e44b4b25c1ac971e0cb5fd40f41f1e6d2a81d7dce2389573d4a8dc1013143e345a907d358014a4cb8e48de22c6e26d827e66c4fa65b7de2eb801824ea3792734cf0c2986a495bb2f50caa4062422ac21a8c28ebde7108931ae877cad883705c03a39b78b62f91cb38f29f6e037171efb2bda099b81390f5b10ff10ccd9d994076eddb8d90b55b061306e93d0069a4cddabb96feba2d895fdd45c699f8def61f6b2f9a7931a626b208db588084c62c68278534521d5bcab59c7cc45c8c9733a2d2f69c9bdf2a3b85274cb61fb22355e4ae62b614b5cebbb7492b6ef9764bc12f65ffd022a150b23cac4e708a4e046cb470e4f9ac01ee508e60278c2efe849cec74ba5414e6be0eea99ec27643bbad067803ea6675c0b60fbf4fbd29885c1d8a4ee5ec39b27dcef696b614dd166a4cf42d9a53989035d8d02523938e4fc81f26db64a9631b0034f4b50a028351ecbb387783a040388343c3f83fa83c9a179f567f86afeaf745212735dcd8b3c5bf1c0c0410d451ef3958620054e393d32955cb3963b735ebda8a30077c34daecbd368ba6a1f2b8e11875ed30cee1d41da278b5b0fd9c617b8fbd822fd58ecae453ac35fa27c1dc2ff6eddaa596bdee3580e061bd2e9ce7c0f8dacbd0bc52728b0a274e6eb4e11d2795f732ec0549ce849fb8b897581ddaa9a8c0f4d9a4758f9da62524efdd88acc07bc46a43ac59d362be40501d93de404285e5ef3861c4336771206ba50369b9c2be49d5a3649a11d35ec0411bd8e395943c10e9604d189c0432d3bfbc55cac0b509ca7b38a0b30ef400ccf397be620f90862bae4ec290c291bd6d1bbfe3ebf3889a2eeed342c8e1b454515556dc3c04c57ee004975bd6dc753eb48d9079c4c962b80622102605ff2f4acdbd30c3d3889e8561f891709d456f1a270ec7a481035b5a2d8bb35cc1014394cfba0d60618e84e34e85bf4ca50d034be5209dc4493a7449ada558f2718f2f1cedabac4fc9a1a18e5b5ed633395043b5d8dd42498a61563b1c27c2a5728e639689b15ab0b02a4de641e433c532630015c5bba366f289718b4997394054d45cc71aa031d5d13d21ad4904613228f706f93287dbb036e031411dfff4b28baf9a25df6b75a31eb1fa2deffd289dafc602b6c11b05635fadb48d4178a89d83dc841fc390db698c8a49b398c548bb3b45d3f52d80ed6de3977f31d70d0f03a53d4b89dc00c2e25792340521dd7c4e922079f987cb81786acbaa101037e6ad7bf78091e26f06ed604739be72e53d94afba808269e63447580466f1044058dd71fd2fd98c5983a6bca2654d6c4d2e0269f0cb442b1a3a87b12b715892912b00326997110a8e82045e701d9366bb34e35af6b0d2fdadc51a61f0c360c26a177d995a6aa8919ed418d00f8d51ce50fe8f206c61627f4c5389759c9e2aa830c812520d72b110bd5f2a7a3d556790b665d6d3d0ab877033e29fd88893bfa41d1fb45554894147a212fde40b8c8724ae1845abb087906dfb59381b5065f063e329ab4eeb8c445f0c0248bcc36b624c4d86b10b6dc8e50a3671f13ab0210e9bf700128c48462b76639db90a0c0c193e3da255bb4e3904ce3357816bd68ad6317310880ae6e239e3a8d037a1c138b506882004f9e2f89c22f21b39d545860a0b59d71f0b19e5d23dd6c8941d99974a3340cfca92ad12e9fa263da7183d91b1c053cc6103b745922c4626e1a424bd67b0ffbf7dbee8b102745df909842b76eaddd41fa981ae96a3e73d5d44857d399abe699abe68d7435cd5c3599b96b9eb96a5673d73473a979e6ae79e65af3cc5553cdbda699abe6997bcd33574d33f7bac936cca0da7c4e308a487bb238e435d9a71503fb938648f640bb7d069dcbe6e790116ee8d455937b6f113764aa1593abf7f87a8b65dc0b71a3fd3674bce499e86e5e641fc1574c2ebd47dcd029574deedee3eb2db64139602b33f43f0d91ae81f6fb087ec5e4ee2d820d99b26a72e92d5e8f720fafc3ff6eeee24f73af77c1a5df864eb16272f716c1864e5d35b9f61eef77b10e6bdb513c2f697228ef72ad84d9fb1de20fca6c1fc1564deebdc5eb7d72943143e922dc859cded5369f618abd699b127dbaaa0782bbb66eed5d3ca6d14f43a46bb114a8ed7a5beccaa54a792af44dbabb14d939fa89ed537962e0d6b4c6db489bbe4d6bb86fdafc69dafcd1f4c67bd3e6afa6cf9fa699bf4dafbf4d3b7f9b367f9a7efe346dfe367dfd6ddafc69faf9d3b4f9d3b4f3a7693fb04fd57d1613bdd51cd1187559546fcefde63251fdfa234a53a38bba69adbfaa6b2d2e7aab39a2b1ff929a70a77f5d2eaa5f7f446934facac4bbc7aa577f8ad252f4cad4b8bfce1075e32d7d33ed665d1dd40d9026c6143dce6aacd034ef43cd19b8f812988d69f8d3395e7fa853d99318350109c9bb3ec07f72003ad0426f51e62ec1d2fc239ff8954185c193ec1d6b6daab09fa32c2cf9c5ad1ab6ba955b41043774ff82f88d5311c2cdda9f03f76202a5a2d09eac395ffef15506c067ad83a63af0a81749dbf8c5ad182cbab508eaf38bb4bbfa7192d3cabd543ecb4db4baa0453e53ad4e4d8e69775e698bed83aa60452c5996a74fb74bf3fea6dc7b16d1cd4219300344f11f4173350324c49bf7abd7a7cf489fc5c625ecc40e0c2813c76489702b31376cff1e835984262b1e463113d7527b4181bfa9c410d37c5e331135c95815fb7c10a73828afa628c2ab7b032586c39dcfa02db57eb44a211891d684038ca64dad65d97a49ee85d1f3aad48d5e89bb81a1d5050a13fbdf251c96cbc973cfaf566a96fd5b6bb3c75127dee05fa1e9ef62290cd08a3b006c73efedc5a83d869d01ba0c0ba620b1415c0ce860e60e8617c7a84076bc7b9b2d71e06a74119c7edbe4dd66f3e6ef206c9d63be1ae6ddfc6b7dad676a576cd4bea7f8d71fd5845767b5ac51416b831f7c4559d44203f75da31679e5a17cad43e09033d9daf9809509ab439704e9c4b53c2dc82d731900d1b8c63a0ba0037771019b29f804513fb29837239eac4f0f4cf50e4a1a5d0d5d04c72f9b79b3f12f0b298a7cbee4dbaeaa6c94e9dbdb506e3b3ab59d45c04ddedc28de93a382d31e6ae9236fb941bf9cf2306cb25f7f3993efbaba8ce0974560efc1b222f1e8fbadfa262100ffa2aca0d86f389bfc144e64ca832462a58488ba2510445f50b9f6432901f1e016f71faaf43f4e6143172be90c79b3c8c08031853dd9e43830c75aeaac026de6cd266fe06290e88a3323756f8d6d3e60967b12b2991c3d0959aed4638b72a0225c54b305149fdf07efd5d8c54f2148a45ff042b22c3791b17810f1b8c459cba32b8c7a69cc0dc8f861d6592109cabc327b3950076ae87faa1b73cb1f2f40032ca32e5a4b33e366962c20dec274881ce2b5c7e69828b728e540c5de674d18ba185d00b75f36dcdb0e96c51a558164a3910411b622ec0008cd66cb4ade5d616695e61e46baac82e13775752b6291cbbfa1b674a34892954be63695bbff5eacf69b9e280451f8c077496122217d23fe9d71b8d0d1debf6101dd26f72adfd67886b1c686326b04359b3839127eb38384b96a4f8945c1faf6e5ef7e22454191ce4e5928ef10882b258f447266ca4379b9f772b8cfde590bb061bd463533a3c5690df4346c2de4d922055a8c8fdd3f7ec3b9962960b41ff225286a5836ea9a16aecda4190ee0650d8211d4ef2134a537c1dafae22689c8906b1456e1dcca82940b0b1b442334373dfbf63a7d7933ec3890df109d7028a43325110ec01bd539edb6175ef3a5fd287fdad1b712ea890c71657f79b2f430b7cd6a025f83bff43cd7aad8cc98d4ab179449036668693edf7032f987d8a1f05e3f4e27c005463445c495b9e1c20f63947ac7cf1961dfa4a9dc1eff4f7055784b033e9087d1bf241083c2e6dbc68d4e1b8fb6e423f63a5574281a24f344db71bb188d9b1f39f1c23aca33c31b5215f259884dbf98265115eb8721a3c1af285a460ffbb74df4c6e419b7ee9924e587b0df0803c7428c4b22ed34f4d0bd4781a1e3ca0fa66d91e9b5c523bc49133c8d58bd96c0f98a13ca28077bf92a52249d8f495729d6995d4e89605ed9d96aa360d07d92190a323a2647c7c126dc706b9bfda08c4a5a7878bf8e587e07a70fd3e6e19e5ff41b01c8866e75b32cf5b353fb682b8ec77080d5548e7d8c58b733564aef1c08961d3a32eb85b8904733079878434404e404d72ec1c0ccdbf60d679ebda8fbfb2cd46d7d0d6ce89c9fe3fae21f2c0d182e277984bffadaeffb1e6ec6f0e44aecafde5a57ecae29af8eb04eedf00faa7b1d078929499b425dcc17f67da3e3a431b5e07d4eea56e4163195e5e7647d48515a6922afd1c32d27c731372e7d27be09827c94e53561c3af098ee756c692cdfec98efba55922c29fa97ead647156dacec53c4b0d462fef5322e3475b3f1ffe54123b410b455ef37b712c180c3b83ccf9b3a741d7a32511c8627912d377634e9544525579fef03c3d72539fbc6fcb2ff7314c593c8eb013f69b0a14db4574d44c14d9d456b4b27f20ad6b8b627b958cd35d8a712242fd90913ab4880686d0a53e46d1570c74432b6462f48d53ca8429dd140860c4036abb7c4a35dce56b50bc826a0355f4b705aef252ac5070ba8694845080cd6b83cd8da57128ba6f06cda7301a8dd32f31af59990cb9a98d2e11d95ae2ec5f57471905a5b2334df6412e88ddb836d4df72182bc77e2fbe8514e883645feca0a7f134eed25a0d79e7697fb531b961c78750f374df0375d97f72e067180b11fb707f2f8603e31fe9b8bb917dc95331bc34a69f35b32463da4a33ff67a2ae5e2fef45c0589dceb960be78591b83ab42fa12814de3413a090854c1b2364c2aa1bde3db81d7dce816d4814e316af5920150fb1e3f6511812bb1195a73e29dc5e736dc0ce40e42683d78346f1abba6a4e274faba988bdfa8d90c2b2d968b530c4a2aec94c42e65db0cab15e0ee472eb05fdae3e7fd2680c1ea650938dd0ce22a4354a8bf9b4178921adb0ae77db81ff0eb5c05a76ac5ccc01269e60a340272a1744b5279d968ed33c7b1258525cf0a4579fd0cf274289348c9f81ad9b7e5c05926a22da9d7949939614bbf4cf469cc84a0a44d5c1c0e9f804ecb4d5aeb0ab989cd603af19dccb9ad5dcd0efad7842b97a1c6aefef1966230f9ce749e4fe47cd3a3dd3b1b1ae8791cb8ef6e182634bb703f49d66e3c72370d8497a08473f45a64bb01aab1f3fdc7ce0a2abca42ea52d5366f52a7424707c8c578be6067059951c75d2061057de42764b3b613a9da2a7dea46bf4354b23c42090b1519bac75d7709f047d93b6261808848858646fc2cd3a5c85a4af743fc343fb9d724d18699704b77059b18c69e5a7d6c47ed6c4270fd90241738233ae5873326c7e59e4782f4fc325313f7eff0c3794e12d4c07fb257d95c796806b9b90aebc654bc481f78544257d277612f63c51b31ec6e238bd1a57b421388c4da68b83413d4a5544c500726b7fb9333db8d2d41c2eaf324ba57da854406ce1eaaea9c6a46af1348b5d109d64fb0c99d5e99ed822295bae7ec54643bca3885bc71b877593f443315d0d3f253c72f76ffe0ddcf7a264db0b8a962210c81672b23a980a1e1b47768f5e4589da63ace047f251ba9243c9d1c0004b11536e6924e48b447c5841d75fc8b82ca4b93e3de03866ec444d110dbd6f81e275aac4551c50d9c8662c0e2a9d3e15a933460e81b9e61f43b107c44b17045dd7588baca67fb9f8c38caee4b3b6496d37a086e00f31e23d2e7abaff81baec3d39f03fc628af75c3074cc5e15dfe48e7c902e10257fd4b03138eb9c0bcd4eb2a7c24455a87529bc65e6e88220ede57e5aa9a727946940aa7900bc8aa18c8da498b80f61085d70ade2735bfff2f63a9e4c4a2b7d5d3410ef46080f25f9c373eacadeb4dd5460e806578851e16592de3a519aa3bf69333d5933582b0941e15b94943b08a32a40575b2b690196a95a49c8a68620476f301dbaec19f973ef8d137c7fbba47e15d4964dc9f9045f467e24d1d9a57c113b37af65f2b2eed87221e4089ee47398b974f3210bbab20b7bcccc6698d52f4f1191900d26309cf4f353cd6ca81b77e0117cbb0643556461523422320b5932895ae37ea12d6a318a83faff35e37511f2d71a6d07163e1d0c9ca9c5d1834b649ad9c18911ad178b12f37e3de4661dbfe23e3476419bf586970e6a648ac73517a9a046c4894f7b9f6c038e16f377bc5ca896370710ff2e3236bd16fb682173e1115789aa11feee9083ca686c159ad65960f3ed386b8e6bfb48935249d7d47f7a526472006b9194d3c3a1d825778f4ad2f28fcec60c9f02de27858b85199e9b589dbfb0802ee1439b4640b11018a17b276b1eae603a2de9108513b1f33b31e4245d83c8216221ec06cf0d249cb77427970077fce5f41998c2c76142789668c3fa8dff0ccd9b30a66754a6670a85c44d00252e1dd051dcd66824ca07e00fc816bfbf5bf34484da47ae3e3d26f07bc38019f6f11ecd87c0b76e6bde1c29c76a239ba29ea966aa75bf38170fb71802fb3b630ae9d35bb9da331935f9faef05f542693662e6501df97a2e1c7a1b4c876ab61df945ba1e1bcca4f0579e8e0c0611ab8a49e4e05924461bc3d8c2be8459aa78fcfd2f47777861ab8fd31a03c1879fd5e89f6288b4384766287fca865c7f74358b25b0643f38cb0732d6b7cec7bfd1fb1586f70fc59f0e990cf49bf02fd38ebcde5475492b400e061029f71563ce70bf260033010c1ffa486a61f6c84bb33e95d675b566d4e608942a397e9a9da4b90d9eee0930832c173fca5877bb9c6a0b71d93202cf4b8c8323d091d6d704797f22a4df2d0983a64bd54c121dc7fec92c96140e969c405e5f48307c6b9a768ad1104c240827c2d0f03e82b25da40073a1aeecb4802105afc3dd91c146119573fc5bf685269ec9450eaf947fb1a39b61abebfff010ee9f6fd4df0da743903fc4ffaf1d8e8ac22d678e827bf2a45f98bd1379d5cbce2c939e38145084014ee003b2b04debec5e0fefe292d8f85d8794a619f62e86ac067727fffa6dcccaae74e090e5694bf40eb465a64820c218f8cdca0b9682cefea2d9599ad555d1602376b6dc789e7aac7febac6b463f66d383200ad65003cbd4d732116e023a357f1d9da50c536ac18edea40f3de72ce860e040458433a00677ada40e5c17502434b41ce00b85fdabd0109b530b09b83f3f2056916cbf3266528564a9d9a837c8a3ac6674a2a1582fd6be0e9af76bc29e6619192a6f556141c1353deef31d28a14755057b0d68a4dfd3bae33407a6de075c4eaad41090025ecd8432a81632c0211c02105bd3a15b05cd1054f64f5e70f08fa6db25dcc507ae28962f39d60a2759b1d7e978927da37b5195e7cc08738c9b47160c86ce564a1b31ceff2a43d9a970175cec2a03b2bb3446a44f01f58c1079fe5d4cb93b1b114e746e7db526a1bd513694c9ed9486d7b20339798267113e47a96e593b550a436f46a4ac181cf83e7e4d2ce04b2a1f5856aa2f14e9cdd3e7c88a5a6eea4a80405da3c326eefc84da76a68d35c97ff7efef4bd40ae4a9f8a08cd597b09ee72782cc4c0f02438ebe2c10b2232c1cd84bc69388e821f3916e0b82ec64fa43e95c38d9d849884290eedecbae98afd4d2e1534fa5757166c649c3180ca940898d249076a621a7da50e7aa9c41d1aa2a7aa556241a5f725d119ddb85039789100aa9b2e98f3781a0e3909a00a2ba4aa49a3ae8d84a8801e06cd4e68bd4aca2f68bd4abfce750f71c9b199ccb732c260a3b62ad478d6e37d799d5d472166d2521d8a6361747b54abffe6bfe69979a9bbe69795425121b2d0289e10478ce74c903cca37f0debc36c26957665d15ca86077980d41b866bccc523ee1fac751372a870b08eb8a9757e5462435fa55732ef0d9dd644691ea57e8a41a933e710febb713750ff32265610ccadc74ab98448bcda8755febc61b203fb40347015cc3136e1e664c2d7f10592dc11f4444281e2ed905888451963c5e9287d6f1ab64630ad8cace6587c6f45c4370344099445e8c69d6f7a267d6991e6726cd27c3b7e5e7e640e20b9987f507c8a3a426dc6de055b49c453ccece23b640afa292ec4a20e846be5c9d3b1e1287329f1b3e7b10ee5679d1d82d1c1381e40871932ed278c003b17e050ef3529292753cc9d697b842fa17b2da9250a4ae29c6ac78e3edd44fd0a5c9ccf32601b063e8f654451953871b653f960f5ce44a0fbddd6e31433ddebfb336bfc657f036325f6c8b0f3aec5472fc2eea38f8c15d0b2bf0082338b2541e2661acd24415e60dc7866264783d12667ee6e3003f121815351aa7d01536c686e1ef2b5e72dc728d53544a6b6c2dd04865927487aee48d6eb75b4c3d183d23a2574aa156a762a9988cf9e60cd128c3b9455a327885bf37d08375b02d412f0b1fa1c7acaaa8c6bc5764b51915fd8bd22a189a3ddd08037c4f7db0a808d360168e40dc04cc906a19131dd1ac290be16ff0a0756f579dabca5e45ac5184eb34589777ae8d3d2411818e461c498f18e2cb890c60de3f0449b5e0684789c17f8dccb07cb02e341b92bb3d1dde5163cf54430d29081667acbb3c23995e0398797c35fbce94b822a3a532f5c03b94827a8e392cba44a5e8209fe3db4102d632b5ae638bbdb3e70679725f8dd85be0dbddd2b3a7a74e3d4907dc39bb804717422f5a2c874012df1aee8d50bd2936e540c459ad2443a998679df1447a29b44d710f4933c985d1e3adc7344b0d91e2239a58c77a6097ca772ce4a22000856305850062100e2f060dc1d0b127da8a8dca76f37588e86f260a3af0f6003b5f77189ebb081609a9453c44b5501ddef3f2100920b1691164b1d8fc8d502144b481d67a102dca94f4446e2cc50ba0eaad0ea23078263882d2c8363f4c3c448bcacbe73c3a230a00b0f8548344bfcf5e14bcd7f71b6af63c55f27b94beca35d257a829fb0fca35649343c30a0a849b7aefcb98dbc12648dbde94775e220bb525adb826f329018eb4fc660f7848ae4e27ad942b64007c16f038f7e28dc83f4cf8c50d1fe3ca34bf0dcc2c49b1d25fb03e02b9b3e7ae4a4d8ec726ddb36cfdfd68b29a66bca28acc0d70d96d97d053a56d86dfcb3123bf130d308f1fbaf73f69da43931bb1edd0f40f23f05c101d8efdd001bcb2480950bbed259cf2f974ae17ab0995cfeaee0b3c49dbfbfb87b343960949652e0e9ba2b454de766f51d28fafb8263e99d7dd1ea95e598ad32ea1cbe68ea7b2961d9754029fc5ede02b35455984736a47c19db3911617236c4a267d0f6e9cd6dcbcc6cbd9ca708fdcd27328dfbe45ed359f211e28001611271e88f80ed4e442db39a759b7e2b412940f8b1bdef46ea2f746029e9e9b30c4a88b9e861be4dba467605a14a0964752451d6430f29f01af804dd7a220d6d2609bad1b50e541cd2ad5cfa14309bc981140322209dd9c813829bb4c86c19738dfaf45275e59bafcbbe0fd7e59b40f4633a1769154e5af2b5e7bbdf5381011a1d09912b124ce0782e06e10c8e4c8060d9f679ff7f80b0a52d20f2d98d4e83a3658560bfaba8b4c4522147e846b3a1f01bae7a1add79563e0869f008cc9ae8d7ad1fc3f19450fdde41c8afbebf95bfb45f5a24ecb638d92e08f40c29671445071e8cb1f724fc723cc4768eb76b6bb8464bbad8f71b6fc7f29665a95beaa57b8c7005082557482cd888ef11d7af82adb3839e854e1581d86601f5eb858a866487993f869717da6de289eccbacc09b9caa65f7dd262bf821ec35f3110f4672f6294722c28152d874ad2fb48f65a4d139d434c32b8ba3243b2b43fbcd03bfb13451206611e9220b5fc69819cfcb839e21aedaa8c652285dc8f081915ffbb06c89f05a72b44048f2c0f0d45ea8ae20aae88508b1287926c918b9c4e4a1795c7d4c2c5088711c43f6dcf928b640f58ed0317c700724a8e34df5c0ac91637df98b9117a06156a51b56e7e44fbb82ba49767cf0dd66aeb7e443f34ccda5f6e314f2b08b43d0aa20ab099f88d9c8db96292567406c11f35dc101f0e37134391b05f1bded7b2e44daa1d18dfbc2c885c21eb2dd583b65794d4a45f005e01227014f568b98a74f965c02a83e44fc7606c1be9b6606124b400240220e317922e806b64cc114655c9188d18e70654b834dbe67045db40251204346ba6fe47078d499c32b0557b605bfe4b138797fa1f1d447a0456609039c7e89a2c52056b42dec45cad27a42da16b51c1523a2925f2a546d0b0b04ecfc88bb6ef42afcd3e7321b5e5c2906a19a3f9091d02616089e7b117b0a11c22d014e7e9239049ac97e3d26e2b4530021c66f1a51ac5134d8f015c6f185a37e6c019b9ae83102204b814a0fb423c5807c048024ba42ca5982b31c797924b077bbc53869b558a75fac1c1de2f93d02749f9b268b58658dabe6271127f0c5fb3acf845b2a0a58456647c49a82590737e57b2c9d6175aa2475d955258ef4aa66035140b23fed4f6365ff5e5eec3dd5e953b6578765f8feab0effc166afa5fb693a22e03eba1de8a116b7d455b36f5eed0f8ed340fe3a0e5819ffc6cac1fedcb2cafe60889e486865f601e9c0fcf80c3db3975d7bac7e9aa856a5254b9c55b3dfe0068d255530f7d2a15cf70e20dba03b2f465b129d2f581dabaef726ce0096ddef79918daf9e5e6f9fa51e54ef6a92a31782aa48247b3ad7f8e7509d2315589305e09c85dca2819d25999a67824e54eaa768256a770a2dc2fdb7bc76036a484b47e35897a8996b153dc2dae728fbfff4a4c4ef8363a1726f3c2fad7d99a52f829606f8f1ef56a5cbb63ba45856c83562f3d200aa872c7ce07810fe8a08f53274ffe2224842a892ceb2a76f43e5c3feba6261f3b4c57cc7f1f5d4494e602378948780678e649c36d806ceb258beec3329aceaa216156d646277baccc514137cb3a1306502793f19d89db77daacc7ca31cf8c7720387c3a6c5f16e94cae66cd652cb8b8451212d75136acfc759bcdfdf7275eab2b483e4655ec3c084e21630fcab2696456004b9d6d6d82d82899d5f7ecba262515f6cac0826c8c84e03050c6a8f7ee65d1cde098f196b24fe41ca76b6a6a4623467f5ce86612fd859dde04e11854b637dc661f88d6bd8b0474fae7ad29486c37a33c506206d181e80b69eff17f32c67284296ebe9d84fe86ac07dcdece54c8db359c3b3762e5b26c086c14e81c1bba75dfed79f02bb8931708603eb2ad0a723dba1aa662dcbd424844025a883d8795a621e87aa460d11708d2811c233d5756230159741e0995c0c97e6c4ed8b6ebbd57f00e71193bbed67bdd588c316bda9d086ccd604a66863fca35daefca8adb42317f2f4e1a28a5bcaaf5fe7550e67955868fad0f034aa4d763b16f80d4024b97c324414d53818cf5573fc030f08807048b2aa5835ad2a59a28313e34b29d18094a8756f02e372dd5b691db0221418b5e1d3b19253c5984bea4677b568a8aed3e4dc985e3fd82c145834d8a232b199e5733669d160dfb82c17e80038853a7f95b41515dbb2d14495f0b0d45a5a14a4c5b824355eef01f21a0227992c722e2789a313c7a13b149337213ced718142393477a7783498fbbad9ed247af67d812c0a9f09d2f328ea3ab39953734795c173b12fefa328e8fe8664de17befcc29aa1d2a04e129dcb24d9d7d05a2ff14676d1fc6d62623325416d25151f9e543274faedf3fd43738033876b58eb1880f5c61254b1c0e9f38aa9f88b0ea256e6c63510cf66c985bbc1485df41ec44fab84c9a9bc396f07bd64188fa32a103f422aef304598866f2af487acf777ac5e0a5a7aff965353f13afbca442cd30589902418700e7736b584246f535dd7fa6d1eab42cf772206e9214194d5b6d7ab84f428ff336e07ad129e67959bad70ee552af13316cafeac5a4e7797c84d9853fbeb4e211c5db82efd49edb1d6e9f40a4758e5e77b6ef5ef5c5812fe0c3c94e7796aca38ec9db6237853cb1835e363d632b209da6efec1530e8d389bfaabd31e59ff6f56b676de021eddba7794e5560b6ca2ae3e75ad96e3c090baee433a99f6a76df4f3cc991e96ddb5711e2174d1f836ee2fa45d7ce94bf9ab5a4e52e330ba27597d54e640e33def5931285bae60c35a54601828e5b15d4121f46360ca518cd49389b191f27e18f3eb6219236a48c9874af499227b28f30dae1a5a006d31ca201dfa13a0360dbe7ba2dcd11d05dab689c30438aabed8fe002af48bf053c3776ce0d9545974503f5d96d2e810d9ebdc2d9cce1b9cdacca9de0aebe145ccbd2f453b6b17a87449a5fc2d759a0c6dbc5c36212e0b0f67e8d8d6900246495399c39b98aa1ddea149a35b0878c3745344313e75e8397a96ab7302796692b4fdee74e9c6761e56c1528ead5f10e3648356b2c336d411fa0af370950d42e534fad9707d2f6345e63e69eddada6ba726dd030a85b2dca6ff1b4c50b7fa742ef8504ad251f046f62e8d65c734a0243c94242e9d32f995efaa285a050bfd2fd3a992551e5fb5c943eb8b527910c5b650cf6759db903828ea2a7b5de6a7240c1a8a9c0d2db325bf8d96b8bce22bf4ce97b2c66121523a4fd216842ebeb0b3a46f11a8944ccd158b8c53c9db5631ebaf8cbc28e3d8d7fb5847461ae0659bc3de8561d80e883220641917a39dba935d212ec17742cdc416375799d5db23a29c5f7d2764a4ef0c557c8381019d5e8abd783c8e4a2ab4ff10bdbc92e75cd75d7ec583505ecbd93226ec645962f5532b14d751918918d027726110a02d21add89e906fcff9b16202626b48902fcb3f819117b52976ba385671e4fc3c722d049d3b458efe4986baae15947be3abc9690f84f87876200473fee8ac5066e2d5c1bad1ae518a40f85a614385dca6e6f23cd2ab5a5001128ae2d6b0ba9dc51405f8c44152f749a17f583a771e07d3ab1cb5fd53b0765f715143b539a66459f33bcbb7ee7f72fbbea0fa9f7e3b73c6335d55358982b1b9348105002965e076e3da374d4471675c958ba4a30bb77d73c5c2c0cad561cd31981c830a971d465912ebc6464dff3dff8f970286bf15bd944cf403643ed37349d9ae4c5918e183c4a2315dd59125e6ce50eec533991ac740bea074e4dea58c4ec43337a743ce73a371cec1b36e05b808f5b4ab3e6c9b293a60c541a2d559895d93c8465f688c919fed19abccb1b0261a63faa50deedb656c4b9637c4a7edc7c47273bd7d2f0e875a0eb29af247790e64368076b016a3e1c592f4df5660a560734a8e49a2c7a8c13d95082480e2599625ab420ec773c5b68ab7ad85047bafc3adc3e75d5746521e3c07930d31260bde9f440f5e2812239e843b93a2911f0a128f4f9dd43cda715f77244c2e50a84faa8eb36cbeb9993b55f851432c8a0d338019bf82bb065a07827f062e38118c9dd8f0c28436b4f89de077d4606f5848d1ee45024b65082ac8fc8811b643bc02511746bb95d7381179a3300ca7b23a2e1b5985422a2de05c8fe4e8a39fa76031f1352c7be4a42542f38d99398ee8d233cfc4631ca45411f3e64d433595de354049654dd79458e5f5bc283bc811139bf76444a0fc277a54ccc841f1a27f3e580ad2d1c037ffee45d3b656f4de7533c66144e80f81e28b31de2be02060ca6578e24458a303205c31a0a07a80c2e4db3e619196d9b898709002000c720c11d1a86e649eefd0c69a99eaae72172f022a2f309f59937237a98e136acd3f0e962d7f0aac201c2e848717a6eb14c992a31b3075ab52373bbdd7bf904f9b90b7516625a1fc5af2508657aeb9bdcc178a82159a0afd10be82c0d851f61e543c7c65a4ebb0a7ebe17e85698b1315c9c066d18d91695c6fda692cd5e1f34d85df698ae4609836ae59d4808f11df0b79c6196a0600767292d5ba473beb3c91671c9c8d606cc3d84a8867963affc8efe21a31c18bae76db6d011baf46561a5d7ededd0afb0b5414d07d7641aeca7f8b90b01336166ed4a0074cdf9abcf5b1b6f262d92a2c3816e6c40fdd12a3f7c0f495ab79ff9890fb20623625058d1160f80b39fb507671a19d42a786357a9d0a9b4ec30a37511fa1c10bfd76363ef2882005817c54d15990c35761d69197e5ac185067d4f4561a9ebfb210d72013a31e56f57d2004fc8ab8998554a3ac0815702b53968914c035e3ff9e68484eca11ebc1508219664537625911004b473ce4d70a2ce8f84f388bc8ccf8af7a5e28fb3ba18973db16d6400e36899077c4cf3b6a25b0b3c7e17762a10780f1153758c2957b2126d11aaa5cf8ff319df4dc093f32d26b446e320f8df52ce63b654c3e2eea062f0aed055fc9adeec6c1ed2c9aebcc456d80edb905e6c409ad7141fc4d00806eea41774acbe0aad09d53a9c43c342ff43ce934a5e582f18460df847d331e4e3a701a237447fba28293165355ba0520a50fd78435b9d11f258d2dadb068c5e419b96135927fd8e0dda1b32d07d2f865f42994c879149bde118ad4597f32dc5ff2fa5476e291d301258dc360df6dfa34821422f30eea111f02c8bc2b199f35e145d05fc4908536c8e4165863d29e7ff3f55d08734c29bd5bb53f04bbff391966ee0e0f07a732225b1f6316f259b570098403464021ddd1a4d538648d247030a749a38619384c9dc34e1d3555a4b5480f730ae946539debec0555d25195b05e75d2ea212a2b32e889c8a3d5a5dce1fbfc52c47dab30b1c0ae342fc3c4c1e1e2e0b436e6d2df90837f66054004e1334c61a452fdebe012900011bf31b21ca33283f6dfc3994d9baa2186542a25ed9394d8f92e48913d8af3484707d2eb9fba62dab7ac1411a7e3a697ba561229fd4b8f09c2f13c52e7b651af585e3fe12b4393c76aed160bc0b9ac33d87a2b264eabe563980520c1084b6c431d6b148183690d1a34cc9448376645c0b7585fb78aab6e3bf80c7b39b116b00f0ea671553cc35bace5a4b1a085ab3aa9a0a413427e4021d8746e6ca0d6da2277ba027cb1335ef2b598d8d0165db7e3c3773815652e4ff3c86ff473bf08156296907344a4037dedc299391956136e3dc15028b879c80623530c23cc56b063acf47b818bbd09118115d1680c60eb4b8a2bc1dac542206e2b4ebe47dd14425345b355e857ccd21867998533028e93cd8c6f62e7fd8b7f10bd3e5afe2af9afb3bfcafea29edd111a1b253ff43a023c226619f8bc4739c115e561908b30f67b1d5a3485d4631926742c0059f5dee7556f2d4597aa1bdd7ea39943dd1bc849fa61e7eec093d92aad28b1c6290dc89f604f32aa21c48c51c04bfe834d48df78efce0f269afb5fcf00aa93e2b700858e040cab70c01844144ae66006d32a8a51279519dbc50f78295b86fef4c2934969daa806eea23919a8c54df369d67787166b5ada837f9ca89c4f23993bcf066a91b466dde5fa8eb3e4d76311b6aeb2615e29ecb6a89308b8d9d25b98c7992e4ccfb934725a25553240c4e101e883755bb8296a7dc6c19ad4d84ea060b11a0356c577eb8feb14b03e50fd27ac54b783dec028050d0f80b5110d5fefe0522f9f323c214d3c470668d3760f160dd82ca99fb74cad281da43a02aae6addbd65bc817697f804cc2b490c49f032d252b80e64fa0c9c422065ad9dd5272d7363a5d503b225175052406bbf91a084d01e1e93f3b05339d6c26a3bc52e9f9c9dfc84ec43d6da2e0002b406a29c6555e15af36b61cf0ed0c6f85ed7cc5fa1c72ef484af13d13b04655e9482555e55c71eb55c0dd1d6220f12d981911073f43bfcce9a7e3970a981ec54a79e2ae0f0674a63d3c8a34b781fe9c661f745523ab8f5017fcc3fd98aebde89051e45e3e7048911907842348de2f14b6bfe822180b62ef48f8caf15bb2175684c1b02ad848bd3fb6261c620a2d1af41c58938f5480a9269616a3a00a074570d8a4944b819de466d23ad1a041aeb0fc1537823bec0c510981066ac3eb27c85806e8b9c53171833b30a8b34172e011ea7bb7f2bdd8521e8067743bfd89e898ab8eca15307813da0146b713bd0bbec3455b819c1ab951fb03af80920c324545fe5a370bf06c945d22be44414786c6906960f22cd72b67980bda95d08ecb95281c35a7dd5a8111aa64acc5bb13ef98fa69db881a99c04caaea3ea85f6211823d60dc4c885ad51e791f9875b2b0c2eb1c48c9ce8ba288eb648361d7ff634d15dfdebf0988770e41847b0f19468f3a2ffc66b351431e9055ef5992c4aca18ae2825d2e7cc21398bb2c208b906768878fbdcfdae5e8407e4379e560e557c093c17daa5e08f157222a05fb57cffef8e7c897089291f7e0d0e274c0cae8f9d2496b4b445fed90572cae0e9eaaf8385387b977f56e4144cff5c275f9924819a2d0b6730dcad88352d730381425a986b4e84af7aa2f12dbf15527bb953fc55d6ba494a631885ec9b94d74055bdbbe480beaa868e213d063bae3ce88b024d7cb0f773da43ef945357a75e812f886a92abf435cd401f188644c459549188ea2d5ae4c80b244744e5f9055ff4c06314b628e5dd9e4d0082436481eab52a1a1d6ac4451a175988e4ca8a69bd2a43013419810d50142eb50da9970feb4d6838d40fdf5cd04544a95aa75fbf89c8cbadab0669a7c35eff9e574961e3416b48c635ee849275f21a2186a860f918c23559e0f3d55672acd574b2ef95c5a1180920787d1fcbb596ef0e1dc3d19b0b03807fbd812630fe9983929a0242f834119e69fbdec0c221d1879d8346835631c76bd60c2d0cbd45a329adbaf869e7d08fb5c9c7b07f3a8d45b1271b194c5382256020fb0f9c3aa5484f020b06cf23fe6d14faa2841bfb79b8aea20d9145d56fe11b317736825181cd1996c3fdb9c69c3bf25469fe9af4f4c3ab84660f789bb6ac96c60b51d9a4c272bb02940e608ec10f7896877114518297ee25393ae0ae2cc8f18b643314d5d0546a5141dba1ecaa9ee3c0286288a9b155369a0b36b797a6b05d3347aff59bc6f69ae0bb571c0b4ab0f6a47b3304e9796362ef5f90dd606df41354d27a85d7c90a909fd3855d2c24087de640f9c145ead76759510c61dc4f1eac3055d8762132a61535e3a5010d625c4161a01ccc55cc854d16cd0e5ce9efa7461642cc3a9188eb3a6a99a09e20da8b8998f8ab3c0216380b6ee856e369a4fee5756ad780a8fc64f03b07659a8659e6de0cc607932a8d033e5c1fd04dfee7328021bacfc273fe1b4d96688948789cd366c6029d7f03e681f640b9814c15187fc8846f2cc3fc241dad4ae826dd3115fc810aba6f8a88146ae91bab0f079d3b56efaf1c3344fe7cde4dd80219cce8b6e1074e30bd6fbc4cb6d67bfcfa6b5c7957ade5c03673834ffe4934ce08000e355ff5d3e04ecbc90b5387f9eb5805cc3bc5e12b8bcb4226e0b86822324b145a48d10f76c231c02d7576864dbdd19ea59b35612dd10b8068604ee4a9257e828099c7fb0dbe8f7ea0df9790cf884f172903c91a3781559950d961609be6f9c6b91c175b692bf35c227a551b57836ad7d1f5efe087f265d1e6e35360b109f24a6cfee2a317cf72bddcbc940d65e0b26121c6569da3db0e121333bb0355dbb872e404150e73d40801d9df081022e88ae667b6915744839c11fbc7daa100227d05a59a2643597602fded7fb2d4d020427c9a9a36b9b18adbd04b2514ccb872e8921aa4b1c52c07fcad5672e2935dd886c6b71edc6e0fd00c5c410019b61b7a3b34f098d6b3e0afb3da02f01b93bc08783db14ec98f9e58d77a006be9c81d5dfaf1da516ef2811bee5da9d597005a7a5c7a9f72d2dedfefcee867006c6f35ad06adc0e0312a59d61ee23569d3b225dafd0867342efb202ccc348bd0ba4772cc6fd21e34baa5db31aedc61fa817b53e4000c022c6af2f38d205ba092a81c0bcade6c76ed19898adf83fcebbe56a2987ab0c962abd1bfeaf8bfc09439ae6365a35d3c97340eba350b6ff9d8436c0626dcf96c1456eedfb11d3222f46f8e44dabd314a297840a608ec577ead447a78e0f0f56e4297947ed475ae29d78d12a01a7511c7e36449ac42edea2ac10453fd163fca2e6f69f9779d9f02c9183947d446fd52d9133955e304b6d112df17516f9ee8d00fb82e419bfc9a7df3de5d79ba3f4d3cf914ea2345b1e32a4192fc086e61fb7c8b2d15d8a774fc3be9648e823fc60b27bf74006627e303742a400769013eda8dedbbbf0462f40814abb5ac1992ceaa303fbb29838829c99e6796335677963d3d53bc8973d4bd65972df74992ecd400b992015af3608f5d1e583de7de86ce9d82d2d6ed865b89e6063cd51b8dff9e65a50654ee6346f6382ba8d449f090c093d02f97e9d0a15315d7edc68c2dc329567e7fb81ed27bdf8fbdf2371ce01af3b66a031588db0038de6c8c0b3724f82c3646ecffa796190fe6f804b703157061cd044cdb3fe63cfe10efe27376a099bd63c2cff6e974c2d0cfe978f78e9008c2daae22636f6d4289bfc870f6596bd670cdd7e531fcc338513c310e36fb4ccdb10e5bfbe1b78facf079773775b5474ab0a1ced517dab407adfa47cce51ede92e01774110110fa139056b79065d70117fc81c28702f8a5148ef83d8e8b4679d93cf1a224d37df056ba8d30e4f93572b5aa3396194667518a49e37d3ebce093b92d430f884b0162cb088c94a3742480e61c03c12ed66adbcbc39212363645e9e7af0da4f4295e7dc9a665de23f58d970c2ab104729529e05be6c649f4b38379ff7f9ebe384e1464411ba0e1e417ece077352b0a98eca3516d550550b1a38ce9c844db2e00a12d61b733b4defa84c1b1a1c9ef4627a69d1789507d1fdcb21f6efd04446f6f564d441c59db19a690b44355cf63b37ffe0f36b77dcee95be7e7f655b15e6700f7377145b2cf2995a19a09bb3f8c63b90891e8937f7d0c42731e956187d9c30404c5483182083758b93855a27814e3c239d6bf9c4a0570cc5b0aaf7f6f83925443ade8699838cef65f47487ce8f8096674f5624acb14bddc92d257d64962a5f4184edd1757d9a5d388f76ea2424f493f62b35cabda54101e258ec4468eaf9e02a602e7e5363e0cd8f828603c332af8df2cbd3bb203dd08ee23aca29c5e606d5515e70bb11929143274869ebb136ba0e2d26dc8668d32072e78ba1ffaf441bd00f24fe70a3ecc6f00d073222b3aa893e4d762d283ef179cd442c0e5d218eb788b869c6f32001184c51aa38aa4248677c54404201c607efd542d88627dfd79387b9b57d2dfcb3f766beaf20fea7a9882698af8885a0f5979feb7ddc7b172d36d0df77a7a23d5011c589465d8e624a5918b7601c961c9c8426e2f73163dbf4b9e1f967eb96e9c428dee93ec961c547222cd1022c241c8be2cde2e65a597784a0847f2a1b9804cc2168c687ae0d26b5e55800bf5d95ecc0a00e03e5cd4b415be98885705eec1663f22e4ae3154e2c38d3f94a2f021efe0d9c30dbd9434870f566df1d5afe4159a8c7efadad4eb3068c7182c5197609c8edf7a25105ae0f1524d4dd846ce6674ff051e2ce336c5f15d8473db1de64b99298dc93ede24352acf80cf59438644523879b311c2bd0d34b4245b38b1d069b9d8689d522e4f86244c60ca570214d7f72dd0aa7017fb7a3ae6e857d4a2899fba1f4017d584bcb6eabc74ac49ec2287e5d757bdb3e19a5a8f9d368f82a1e57619741f21aaaf48d425285a074c449d456f5ccdb720d2c9baec9a9ba1a36d5ded75c0661835b3b9521e7b9bb942cf54a7d23806120f40cb1d621eb2183e6a33af99deeb35bfddd51f3da579f5d8ab208a21e743314bab8d9262a4f70b83dc2ddc57d64321f00ca845c1e59b46046cb640da37762e2da4cb0241ad43103ba6f6e845a208e59361db4ba730d9973b86f8662a0ea877acfc750df46cedba07d11a51dca803c7ad843bd034a847a7985d967d40c0c040604030600809eb957a7b60d7e9cdd25fc6a6d15d29975ab95f20804c828c9debbadedbda59452cae1078d082d083e982c79736e8cb46209ca0c4840e4a4e121e6dc58898794374a2e6c45f1293937668252264c8fa5394e61e7c64d29a0ca7ce860d222c39b73e3280f5a80f5b4a48615ab37e7c63e13d0126de6bcbd7050b8652f7edf25a77bf5d6effded1b2626e6ca54942318580072ee8bd4ab612a870ba2c070eeab04015a22ce94396fe1bc754a766eeb653960b7d53ac0570e0e404b54df35cedb4a66d4658f9cb8b8f959e9724111323ece6d91be98882ce941266cccd5b9add257385f7e6501e5a0a425aadbb0281a308c1ca9418773ce8d272e64e0718483c9b92b520868f8d2e2c2ab86185ae7ae4db407375736864c4125113977657a525375e30a07961862ce5d7d2b5e8cd9088b3187878b38e7ae516f7f74d72b176f8e2c11b5da02f6d653a02768d9f5400dc2a7750e5822ca84a846bda5f1f6ea0530a6313016363a8004363546ac6a851dcead9f9249616903c313981f34acd7180cb01b4d64a872eedd6fa07743962a2caad0509d443968c170ca02258b9c538ca33716df68308acd57cc6485aa571f79488a01007f3d63a6a6b1dff99cdd83c45c95a6eaafdf2c363cc6389fb962d5cb22dad4a1377e33873667681d2cadc345d7a27411bdeb96af0e6ad1555f1dcca2674a7a579fd140e11661245bf639a771fa9c6f11f565bf80082360244a44c11e2361a51914ecabeb243d13a2c52bbdf18b561f7a8f75b5b21032d2a06dad5432245a66cd6429891ab71b327a51a7a56eb9da0d89782c6c4b7e537500d3c23628fc91fd867022fd792d21fd66621931aa2a51b2d75e6b99465a0f1687d6ee23096287068146d3283fbc9e7a0df2d7afb82d9365b24474e816b9e3705adb5a297edc2363e401d31e1045ad7138cbd344574badcedaec41549a060d66132ad5c0ba9d59097b9847f416b1eb0b60cf9e7529607d733e17d0a58063903aaf6ec5596bac8043e9bc391c83d4494b0fae91d286def87daa53321d5ef84e2a674c0ad2495dfc8173022d49117de8ea348a8f2757639693baae1c581d384ba06c083b5f18402b8c2f0cab7f359e6569bf6a2a78efbd2d7830ce18678cefbdb76a518abe96f6f4e60ce60ce68c31c6b5d6fa82d5daa5a6427ca03d757befbdd65a6b9f44d2da5a6bad6e7e17042ff8f3e318fcb8836006fd52e991520d6caff51674ea22d01355050813004371461bc91285dd95d99f12b951072830a812803b0643463ad6c01abd3a1217863ee26a2db4d7c8eef0bd1883b584ad6d2330e7306731e759ceb49cc79cc99ccbcc43a537ad0ab1740a1520d013a0e8d83bb0bb323b0a9b004af463c2a8037ae282bafba063408b6c54a8baa8dbb5fa25dd55b73e8212519a735824a90633a69146298d62f229cd292d7a9a338016e1e815e909fc3437a21c844ef3139508749a2ba01265a7f9494f60a7515ad2663aea24ad1c845b7c9aef72976f6f49ab22ad956273e6bb2ce92e7fe3a8ad25eacde96a0922945a4a69ad23c63a22f6a5abeb96d36f92e27a8d8cd6d6686cf5f0580bb24414ebae5f2b2d6f57e05ae38cd586561a7af31592813a66248aa7801dcc3a15aa0f353f7a270b54d0f5ae97191cbd133bd6e52d5728675da16a6d261b69d0cebfaf572be078fa644e19fd3dbdea15f9ab444f5c1fd5a07a456145c4a9eebb5b34002c6ebc38f5ce4a86c20918a234f97a673dab6975258a52efac59bdde596d1c254a3dcb747b481fb27480f5ceea76c966659585d9df15aa7e5ab2eab5c9a94679e01645dd220ea63cf9ceea218c60a36e113dafd215aa286e113d2bd562ad62cd3fcc355a9cc14b4bd051d8300c75e834ca871f5225a1f261013efcf16118566b44da7e82843deea99516d70d63b846f5b1e28a31c65b9ad67b63b859b474f550062bbabace8b2eba7a2d55d1d5cb47165dfdc353abae36b4a4b59a97ae5a4d9e4260a4b40a04bbbb2976e8ed3e84e206ed48d84c7190199da2eaebdd36eb4435cb074f13271a77afaef238d13c54ba7badb5a013bdc9a796c7094f95c6d9eed5384f69ca43a5696829106bf56cac51d51eb9f65e1cece5a9d2389e297d75949c8921485e8c71ce190cc350146733da9007f67794479e805ddb3a2011445e1a8a241d411ca9b183f6e2dcbdba4d8e23a9adaef6b419475aedc519a4c2436575b4baf855875a8dd6ee56d87ac248ad091edc176b89ea1ed86ad3687da4b5fa687324a87f82eaa592a7a504c65aabcdee56d86a7b637d8ac71d913641a0b4e9f1144a1b1c1f3e85d266c6974fa1b4e95960ac36b49ef83cb5ea72f4d5a9c9029398c45f02f96046ba9dc788b61508a57fabf5bf4ab9aaf4d7ef454135b84eb18e036cc0f2bfd68bce741c604315f55147218abefa00dbfbac534173285230d1b31f7a531f4f91d0afa750618928584150e73bf4ecb499a780268aa0a89b006d4745d1435187cbd507799e34d6eda1a740c6d90a1b804a84ca32bb318da82badc411660c58848ed7ad9603cf2c87ae4ebbf7de1914aee2957bef0dc38c33167a8be012bdc5d28cde22b5beafb5b32f7fc40fa8f5bf964ff416f37d5c3fd838e8a96e45017e14733fd2c89ad3c93f92e409b9ccf8deb2b495ce6acdcd74e8d96c56629ad6d7abea7c3ed095403e1ec9ba71d5e1325873e28f2229ce667b452547da8cd26b2bfe1a3ea975d6c381cc58ae750a0473c5f6d61b829e05638c6bfd5a97dc2041904b915aab954ca7c6213a0b31287a7d4c29f817cf280d310e970599524aedcce630152949857288cf362a949d82ba4a299d51edbd72ec95ba380c6734190cef1545ad7138f7ddae7412eac79608fc20e7c71fe65cebfd30e7fa41fe9a6b0dae4ff040729ab2e4830d95767a8b488c496324860e1a5efcd05ef4db26042c0037c478d30b50f24c9cfe1dc73b8edb7171474897dff1a192b7e3543512d4c46317733c76ed3b21381e03b1f2d84b943c6c84cb42e2638918951730f01904dc90e389cb9117bd169c362053dee604287936d7a0e46910437c0fc249943c10e2cd71b79b07dd7ce8e6e24dff8dc8df6e98f3e6066ffa004a9e9941c9c32e54f26e4638338ff35dc9c389b6e7117b1e1e0fe2c1cea3d4ebf5609c3c57aae06c35ad9141caebe4e152e6c21815af321727dd7a1efc8365c46f121d08fda6554a7e28997abc0486c74bcc3c66c2e43166d2e5710e373c7601943c6c4dd374bae99605f010d2e34cd98b2132d0a1379d9a34eb4df04d336fbac7f0a6a368c19b2ede220e7c84317d690961b2c3692a71e14d0f40c9337f0028793c5edff314e77b3ca8e4f538a1f95bfb5be6af7b1089a14b64e9af792b947b7d849d7b89c4f9bb73ab946bf3cbe3bbee681afb7bc9fcae575eb16e4e8394e5d861aa0990a3f30ed981cc963132c67a38b7feebf41600e98c0b409ec67c386eed69a085262ff812c3911a48ce14d45cbd008d989316acb94f2ffc2dcbdf1c839277c361f8c985c718bbaecaf310b9e1793c57f2788e30bd70438585a31db29c5bbfcd16f636176fd107ccec1842e497c505acd346c4c7dbdc4b9e2dc4ef38c9f91d1f51f27644d3fc79d3349d5a78d3c9ebcd216bde741125cfd4af1da7879479ed40fe53f2eecff71fb0bcf90fee53f23e5819f235f16bb4ccd76a35edb5da15395fdba9e131350fa1e622d46ecd690d4b99afe9d4b0929a78d6bc2e5922fab59a53f11689f84a1a9b8a210684e9ac353dd101030d598ad8a8495d7991e1abe1668573531f094dbeaea0b4b09981aae6b4561bfa5d0363917aec21705357e8af8b567e7fafccf05690fc7510a6699a6e3aaee49918bcb692f4da7b4a9ebe95bc9a8e539f0ed7975fbae0cbb3fcd2495822d34b1f32e2e1cb0e4a0fc1125d2f5d845a59fa089cbe2ccbd22b8eb2eef8b2327d09834fc9972037b6bece52fb4aa79b967d597b5f5a65b978addd56f23416f3d8c51b78c020bef6fb9a7f2879b515467a68a9a14bc9961224270ef3613b54e1d10298b11acefba4f4d7754ade75b38a913779656d7a2824d985ec224b9224b7c80cc8af271d932e3a09f2a46b276d9eb4baf2e413214f924fa49ebcb2f1e4159927dd2c79a4f9e3387ad038fa902daa4ce3383ab5f2e39420f337896da283124fc3fca5325584aaf7d7714ea5f4d777607f7d884acd5fab2651deaa8a86bf5e96bc8b6ffc367a8cbd3c76fff2d8459fd663274b1e2e79a311019ee6b4c6d39c26963f739c25c2603f73dfcd7cc8cedcc8888b9f752035f3106ed12cec673058e02ad371c58613937376844b09c80c5c74cc4873cee8d5959f417d3dc65082feba868251240caee4cdc8c09297993ea7a2863a25ddac068d5be8e0163a1bfcd6c1483a3a3a3a3a606f5d074b47ebadebf4aedeead039bf758e7eebecf8ad73550234565f3d222e543939b78e16ab2e6facdce0f0c5e4dc3a586ec8414a892bcb776e1db135695e403635c705d5b9759a44e0a5cd0c37aa98d9f23ab74e946f8bd4961e5bc43439b74e181d66cedbad6f9826d365afbdde82bd75ba7598d85bd758b6e8b2b96cb4dea2a26b8edffac86f9de4b76ea1654a8aca0e2f39589d5b3b65bd70c31c222c60c9e1dcbac71cae2528499cc86c9d5b33bd9d7a8344eb07123152e7d64d1fc81155b68255d29329e7d66f6eb461d1d5c3ef4bcbb9b5155ac2ca9cb7bba655d3faeabbb6a5a5a6e677cdc9ef9a0cbf6b576fbd9655d3aa79d5c0a4be3cb921568686d79473d76a50e99023c46a07a832e7ae294dd130838e346e9e1c39778da906333e283441ca689cbbb622062670e024ddf0262becdcb514516062ac318325e6ebdcb51a68891ae6bcdde66d32cddb649a5e26d85b37b16cd1f559a1ea3e53ebad9b5dc8dfe69159ab7e9b2efc36af6a1c9951c3a969ebc9b94da41b5173c814d18185eadca6d22c87df171a3a78f5a8ce6d32a550c6e205178d252e849cdb6ca261079b2a45b2b4093bb719f5d5260d12314f4dd09cdbbc35ca5ba3f4df259c8bc30a55dfa5188e92cd5b2fb7c894457e974d7e974e6559fecab0930b961e5860006381e4dc25125603a4abc90c32358e9cbb540a2bb2552606468ccbccb94b267a068b0725a5155c00e7c7b9cba636b4449b396fc932b28c2c23cbde3ab9e62d29e737b944d2f09b2449f21781ae1e584e5a76c8e2e3dc24d2882d3856472f4401513a37a924001d5f36f4d042ea880be7269940a0c482960cb030605e9c9b7463a6cb8f16689a90f0383719f5360a0b489cbc8ce5803a37e99b424b4c99f376cc1ac3de8e366b1cbd46b0b73e62d9220b66c72d392399df63eff748f57bbc2ab281032acc922f3b769c7bcc81e486992a6f5ac0e8e1dca3d20a1a3dbcc2cc80d4e20992738f3ebe6ce9fd58aaf1c5eadc230f568e8ca13164ca8ad8b9472f698c2c65619ae1abc9b9479f119a111aee372d8a16458ba2d15ed068341a8d46fbd916cc6c0b663dbf679669669966b31efe3d3b5a9acd66b39f15adb8c5ea48a2452a7beb624f3cdae0b7f8f45b9cfa2d8ae2ef024e52c8a2b49c2993e3dc22d28a223dfc70c1050b48326c9c5b549a3ab3c6aa052b2a58e716995440464c0c5ea670497939b7d8f4564a8f2138baaab47ae716a35080c16424490e921d5de7167d6768893373de86642159b8c317747e872d7e87337e87307e876118fe4eac59d28206264e64443977889480177c3576a8e2c2c3c9b943a59e245da4fcd040018972ee900a10b02730d6f000c605e70e9b3e3c9933c3c816234d66387718f54e9a6248a3e45ba3a5e4dca12f0b9805dce016700b0896d17e8347200882e04f481692c7dfd9293b65a79c73aeda192a1fa1f99d9df2150c3078cc79c18289d396f30d0f1632d0280325ecdcb9e96d0b1ce76d6df1d53a86539170d85b8cb117067beb18cb16d51df8373e42f21b2f0df98d9d88fcc65711b07286ec45342c8164ce55304dbff08909c74fe4aa61cb8c0ad212ad86ac712b29ea242998185bce9c2b800c45ca4c32e7ea4f47afecc4391f2ce758a3d2e64a9339477d4872ae4e9cf3d1e11c75a684957dc06a616927eb852c5b9b21513c55ba946e447a18e3eb01d13bc710b6d210317e0029e33b891cf1c80ae1b3e6f459d33b471f5ba94f183119563bbe7ce708e2a9ca15329f35206c3ebd73fcb195fed429323e29a6730c51cb925c40fad12184f87c40c1beea70cea7fa0f8cf30746efcc399feabd7304b29502f1b8393a73ee002ce88083ef1c7d9a900971d25f73ae3a5dd1423a71ce67cd39fe58f1c10444e61c95e21341e6dccdf104e91c61a5948c71f22a038c1cb221289ca367cea95079c09095733e2bce0c7a9a9e2b583ae9798e189039a70275c242990a3f30ceaca3f6009bc4409773d7e9743a5dc637a8f6224cd889733e2f38359943d3d52ec1e8af5b9a739873982b26b3d5fad5d51b667763b544f5eb955a9b57bccdd4ee154fa9e79c73de275813263618dac08409a8a63037739cdb99046cc51cf62c8ae1d344517415664933cfbbc6a825a88739475dcced9aab316a89ec21a59e735893727bc55312849f0b73fdab5a8219a417ab19672c6c0032112ac776a31add68e5e5d5086bb1678c45608cbdfacd3a7cefade6e301b9c625dacc88ae5ea3e8ea33ab71d6a28bd79f60dfa4659eccd32f3f66acdb24187144d7dcb846531f7100e268ea2b1e63e06b2ef75134f59cb33292a5198219e36b6ba534934efd38236b6ef682a6ee53a906b4d67befc5f482a2144da3a56975db83c7515403eab9f71ab5ba1cd134324f7a5c330369039b71c42ea577c65887c328955a31959e00d97841bdf54a56abd5e4e989eab8a4565e4b5fe9899c010b5dabdf0ad0354f5bce889141840acc49a950a5276c062c343d513360a1ab8fb38b67745667e26c667338036be64ee1ad67b1d91eebf1c31320584fed0e8aa6581a3fd19b6645d19b5649d15be4b9d25cbacefc161d4b63d1e5ef7acd0a3d6d5a1a77492133562cd8aa4b6a0c638c291572ace3001bb4ec8e6e6c8932896be9141b300a8023b03b9ba3140441d05e2ada5dad2482aeefa0a722d5614c79c474f5a07ba36602094e101f3bd689507a63fbb888e9b44eab532f79f649b188aed6fe98dbb50c3b1848da2a07b6f737084fc16db9da5915770853eb22d8229c15ba602a07d5823d1eb2bb2b5e1fe2401058091cd8116489e8d3cfba9a985bf2b03dc16f1590deea4660779583fa55c6d12d1176b13aceeeaad7c6587b6216b896e162685abfb0781b041684443cad8fc8d759123d8e95c45e7d575de3ca2fdaba4e15ddb2a3ae73a54d2aba567711d098691a5343101d74c8b9ddbd7e8b2897246532b4d0f0b1e4b462062a365ba0cefc48c3e2acae714af4367a8a6f4e04bba34edf3eb5ea8a7ad188b6baa1ece222093275675c2a4930121dda28a3755e8851b9d83229183f8d5da0f2e540660b4ed2a10d4ba6755eb0417f803ad2a215180da455111f49d668d150487b6ca11169fbe972c5a6197d5a1445abb575a5438b54ec101d6ae969f2a9d55617dcc21699acad35326cc9a9557468e38bd67901062d5a8dd12515183a8ac64f1a7c6ab5650d8ec40e67d33f412abac800c5c8ddc87d22ce88cce8eae7f5f7a2673dc63da6642f3ed3e3ffd07bd1e9abd71694038c444b54a7315e3c5d81831aa82b393e186a1c393f80a21a5316c54248d6b9ab526d9a4106c6922353ae66a038bc204b142c1e33d2f468972f3e2c73be085972ee9a65e2054d569c10e9214bd8b9eb1636d452d126bae645ea94fa53c751a3a73e445550ea2350fc3404464f02e8a1534f01851158a2ebe3e98fa78e82fa8ebaa6bd5ecf066593d5ebf566d01c3e671749da8ddf419f7def486ca3cf8e02e933cef40aeb33d5b2d24a92c53ef8a66c8a707dae4735a9c777c5b1f4d92b1393cfb7dc1404e7bb4255df10aecff90aeb33889c7358150b51d68bf3d91ef9f8b648493fbe2d8e2526ac2f9edfb64710a8cfd9ad6fc767b755d9627dd6b2bf169f2d18906f1b26f6d9dede8bcff7e86dbe2fd24dfa9cefd2e7245a06f81d8740c9db4192e56d369bcd7739b7d9925cbd8db4a9b17908964884db5c049bcda9cde636174e6ebcfca45cb8387fd810464e922d16645f36dfa1667edb7ee7e777488cf96de277c67ec7f18e8b51507e0707a8df916ac1efecf890d497df71104ade8e08118e1321c22992322fc21f50f244e49c6edbeff7cfe71ce77247607cee489bcf19b1fa9c3ba0e4e53e2879b61151aafc085751f24688b7074af3403b0e04647320a0154cbeecb04549ce0e5f309c401f00bb42038e0c38c2da0045c1e18112f0dbf6b69fb74101f2369b0dca95b745a1f136f7a0e4d97c67676767c71b50f27678250f684a9437dd344d0f22618944b8e943545cbc7933d198a68b608e304dd30472d374d3344dd3eb55c53275a6699aa6699a2615afd7b9ce53943c9d98cbe572396740c9cb39951def7e963c1781808080fc35f81153b87e84a3287923767676767676767676767c01256f4744d98b7005943c11a28dcddb3c0125cfd641c933b16c3c7e4cf6187b50113eb1763c46f3218840c1a8d863ac228f91942cd10e8e1db509eb710d72802b4b04e4d82b9656f57afc26873566b4c6a492cead1f339123c683982419a69cbd206ce2b0b043111f6bb0921b1949985cb93202e7dcd4d7ab518f81ac1e080808c8392879400e72f03b2eee6009f91d3f51f2766c369bcd66b3d96c364740c9b3f9ae91690edd1a058bc69b2e9a58496fba51c9338b4a1ef6ff91d2e57f9ca8e4fdf8f8f8f8f8f8f878d6fb300dbde9072879a618124979e1b19b2879d80d50f27c8c42dca0f5217c83921742c8d56b9ed7dadd12d95c7b90f6217dc3d46b3da6775c6b536b1d385b11cc19772973058915140c6c95dadadadada0a4311ecea7215294008c15c3e207b08823ab62e8fac21f2fad5d9b42dc4199bd2d3d6d3565757176e6bcb09d75a6bad15c536ca14a43aab08051450e84ccb7c65434c5d74f51830fe82bbbaba30be557430e842c418cc983e6db9e8a27fc11863105f01e102d7c88ddce84b66e1669a31a614535cf1bdf68b1c8cbbbaba5280d081d1cd185f298bc12b8e2f488a5b6ceec520f81464d36e9492b4c6b991095a04ba1da183a7238438d385bbf495a1ab577ba33c6d3d6d3de5e8d1d193a3c7851e1c3d377a6cf4d4e851eaa1d1d342cf8c9ea41e193d317a60f420f5bc00d1d5e3a2a785aede73d4c3420f8b9e157a7a73e4dce2e8ea373802c018c9ed0da6b71e6e3cdcca6e6e6e3bdcdadcd8dcd60cdd6b800d4234b504a5e5ea49d6b1d2d35ad5171908ed3297eb63aabd4a8b7402902780a359d9eb0eff186a0cf4c411ae5714b5b634cba906209c381352140c94b4b4b4b4b4b8b8b8c0281004adb22af7deab775e469cd1c45918e2d9571cb4b8b8b870546b4a581818d6b3729d5c5efcb8b8b87ae8fcb87e5ef7de5bb57e6af08f8bd65a2bfd5de1e2e28a32c24a6aaad695d6947026d24632ea5e926be46a81c675c7686517d872dcd2dcc86e686e63b73337b19b995b991b995bd86dcc4dcc2dcc0deca6c30dccedcbed77fbba79b975b979ddba6e5c6e5cb72db7ad9b961b46ddb2607ad3ba61b95db959b965ddaadca8dca638905a4204c37b2f78c799e6a277cec5eff511e071659181d0ae87efd55adf8ab5d65a5f8d20c9581d04154af1438b50f8892a2e0974675d871279c0ee2c0ab7a8ea7635a92659a25d65fc09faf8aab8453b0f9080c369bdbbdae17062097623c04355690708293d0c7217dc91184bde0f8ad33b7731e721ac2285915de35fce74be713a6a85dca9d4206226ce421104719e3d582b14d04fa69a8b0b38dc0f082dbd3ba877a8003ba19d7b9bcbe9e4723b5a34c28f6ef9514a290a151310294e73d118e31144516b1ccebd0ef9bc96b2328b9106d5a74236052d0a419ce87af7a1ae1e1d2d1d2dd5a3113c70916c3dca3509cba0a3b77ca268ad16c98b711eb4b33bbf41eea28b7e64f45a771e848ba1b16882585e24529dd18f1115a3664898000817b09eb191911bb99191d6888ba1a98f346702febdbbf45a4be9dd699dd9ee87ee7e9c9e20ebdddd0e3ab07dd1203b85ba58a4c20a59af3174c90b29171529b01b5d19d4c6d038130505c5d1db66b30ee21aea682545f136b1e18c0b77f8de203d04e292f4bda0484f84e35596118a8a8207f1ec73b82b64e9ee0a8962034abc076668a3a12b64bdf6c0d8fa369b2dc74dca2dca0dcaedeaf6e466757372abba35b951dda66ebe5b0e3726b7253725b724372910371c6e51bafa0dc9ed88ae7e337283ba15b93ddd88dc9cc617892e298638dc8688b71b6e426e416e406e36dc5e706bbafdd0817645abd2550c2905ef059331d500db6c3bb67b2f48d2a0589c8f8072efbd57a4b3149eda9d978b73ce60e8240cc3300cc3522b34cd843af41083a178af19d16b566963ce5924b5ad04db386612c4b6191749e632bbcea0e3acc360ee82e168062c455c9ab3297b71c6172b450aab31e7662ffe68c1cf8fdf3ece99c83223c5aa44531c6da5348b3338d3a5a8324a215605ce665dc4510c694122a96d25d86ea609bceb216d766734b12a3257ec238d56f2421f4912631cba4edff2ea5288555d7bc72c2425618c1883189335b2a6c9fb538482f6e482d000da631d8a269668fc50348e3a1428c6d18baebe71968846cb898f3fe8943fda6c3c64904d24f56da77c000936db079dd2041e7691529b88693bf54bd22c1b70bb9526f0681ef6d45ab338ab8123bd3a454e85dd893a146774755c2936b3ce2c71250d50a044614da15547da5d9ea2edce5e6bad1dca4b4830120cf923c190602e94a2a28e40d1235134ea080cac82a9bf1042a860473e9fcf377a19bd8e6636cc9a442691c92ab15236aa3a55272556090e5068bea10a55a3f0180ec361980c93b948aa312a12ad4947231986b35918cec4d0462d7d605ba825b25fb7b99bf45687c25af77bb3cd65246befbd4e8a157441c565e907f094aa8ad5ef12c812e86fd62432cd82d0df2cc8ac49649ad9300b320bc234527aa224291d69945a6b31b6f75a6bad0dc14c29b6d6adb594524a29a59496e468ceb4dc7b6fbdf7de5b7135412510e42181e78661ad59287eaaf7da4ab0d97b71cef7b6503305c1e0a724f0d868570c45b7ba200f22cb9998e9893c2695f45b7bfd5800a2ef53ab5fef9f747e6af5f5e6f6f8e221850a10c8a12fb0d9d719dc17d6978f8a459237449ce2bc215323ba518365c7952b676a9853b37e1b0d7d2db12cbe9818691271062f18863a31cec4e03576c30c0f4a7e3a808d39b17222468e08667c597c419136527c6af51504ccca8befa9d5d78e97f2d4ca4bd6db70f484e836fc6ddb607ebc83559bb0bfba13ec9b00f71b855eaff727e06a6f8e94670209ea9c1773340d2ef95bfcdf62c801d689ef17cb2ad9246a5bb04a33ec12c80a558f444555611b559dac94cffa9a90e36ff4a2a397f137feacb08bd9a9dd89a198db275c303030bf10c05430b09ce46a53af3fe13e06411154e16de8d7ef781d749bab48d8ba758cc7d15e0a5a9d53ada0718ebcada0b7edd2abf380ee480c99a07fb791d197106c0876576bd2584df2d9481d06b723316482b4913a841a238914d30ba0147f7083c3f97218c7f1378ef497e8684914be3aadf9e2a8b5ba0af5e2b05e7195c0b5514b50bf4a5fedcfeeeab5514b54b7f6e2b0bb9aad759cab98ec0787c1e8fde313b8c0637c71e41ce94563d2bed0dbabb08c309d3847579c39b7e21c6d8da5134f81210c7b51ca4e58029cdfa8048d777541ca0cd5cc100004010316400020140a078442915014456922ed1d148010629040744e34948763b124c55110044110a38c310000449031c4186688684701ef02e9e9f10d5928c8ff26b9734e1f04f79c4fa21bef44782b64cff33b2f6b34431263acd1a9cf0aeb41cd1374b78a6dacd518332b5de98173d1dcccff18a7a54196f43e579d8bb618906c0021fea9ba38a1c086fd556f2bf2d9050d1d075559a5579e76f5447f0bd6e87211202510d85d8595881c7c80a9cf069a92c668ccf222a71e9bbcbf403a344f859ac653afd79c2a37306d68952ae84a9cea103090d4819e2475e42caee6426a3240bdd616122147a06f8f162b742e4540dae9cf0a951ae0206f63548e2cd3940fb1a7cc335351f3b3a9b0dcf0d93763ee6a4eea2a574f9138897f8d438c8f7ff22edc651a75cb9bb6d293cf3c220ea9e56eb05738449466c793c48c33b89eda1105ed81bb1e33a5c36c4a2719471d5114ce19626ef769a5bd894d54d819fe8b05d3f510f43dd0149f5da6038dda77a442643ab0b63e51599e9161ce58441867c52513c2b3f7bb16344810dbb61d34195290ff0a7c05dc48d608632fafa76d7055cdc17dd1753aff69aa9979535a61dea977a427894a5710a2622eaeebf1269e240a5a47f2ea109131451ccf1ffe5367a0bbc0a74e8462888149fd9606fa54e0cd81a7e2bead170dafa0bbf65fdd151b543692095e9b3bada3fea9dd4180af400f92fec9e014b979b1ce02c019a78a4d81a6ebf117450246d3e46d4d91462222db459b2821d3ff2d38a851bd4ed6252d21902575414b51a54a7c7079afd43ac95e726375c11e5a26074cd93b76baa5463bd34c99f0a5be06f13d7f9641b299b4b0540fd3d5f306a64824185b587e75f765780bb9f10b5af5a41d2de15ad4bb45bd39cd2b07015f22f936702abbcd3e99799c8980b432e7715c981b376d5954d9de16b2f7089c7b4ce816db5b0867681f271437ce76348080387345dcb81406080b278d16e17b82935b40f0c32a64866e70b824dd6ebe28722686245f67e4804f6826c8e047236e628985b7827fd4c0076121ae477f46a93d6e2fcd286b270091dd88e0f6b06b9e503f01b7514cea2b52a3356069561365b8df78ded3ccdebb3c3832556c3265dd675174c865c54126cac226118c4462cc1c89bb1494bf36ea0fc7c11969937330578fe4075fb4234985809b38c547cc283e2af604e57f9b92c045976e04647eafcc66400617cacc00ced91596e35e78b57da2bab2811a6ca34993c11cd7195a7e471da8dc4f355c0235dba8fcbee36ac235c09ecad96d75bc3ab31466a426550e0d7cd2e114d44591a58bf87e66b6a5d12c2adce5ef6b50807cb8790678d5edeee501b427dac4e5d01fd93045cd6c2ebcd0bd4d7a5ce1159d08bb5025a6619ebb839a2c1598255a546a12d5ddf8a55412d12fb7dfb5ef5ebe6f2fcc62d143aba186ee84747d02ca6f002e10839796a9972214da513cc96d8a8ef20743ef19104db649d8aa46cfd5bb732a75763c156900c24655d942392247878222feddbe5cd4492a468e7e58600646d238600902c6ae3f6194ca4ee348b025826e317611eabb0374f7163aa038095d1c62e0900c7c1eacb408962e14eb14d3d4d986af0e9f5ae7eda2046dd85782ecb2004d1c01212af8486d0bc4615f192d0d190f4ce0dc0d5a45b2af7ab322aa4bd126c4d58f2e205054395d51681a766e069a12a9004903215614f227f740c11152ac9f9a54950ae162a990b467095abe984f0c45a1d3a904ced3ea25c85e1d076d587630a090096e0785e4ada6a63a6d30307b3986ea8237b4409cc7a869005bc3f18876e8b6099065d2a32bff2c0993a4938c188901c3655b4ab97a3e6cbbd596533a859cb90a283f4c85959b856f6fa1683721e3cd4c0b2727b30fc4611d952815ddb7272b351990a9e1da47219bc3a71043be0b787142581e7837420bac917928fc082c0a968c0588a048ef610d1b8437a9c139e5fd04ecf810a2c990baed356e8ac0b532300cf46c83eafd85a31a1d9018e8b286eb4de7a286f41664997faadae4e246620b6293bc0b29820723861972b88a4d71081d209bd183749a6ca0a0fb035ce99beec0b0c56a56c974a49dc25e58ef28908996b72f783a72da51b76b13a454666d829b5a7ddf1447dfa26f9fd9eec236e9518f8a8e646daa25a8d6a34de7458418a45b2373a36ada75d4cc028eaa2786ddea03776d7d0234d5f48ba5e978ac7f68076967733ccec9ac03bae1917267a9992d530734e3fb0172b242493e3d0a97ede939be4ccd985a74e7e96a2e78121dd4f4132271b9f27c88bb758312218532a6a45a5af865d8d0cbabac64c0f200eb3204e2cabd3e53b0b52b7d331c5860ed66fa8640d7ee748c41dd75107e29dd162ed220f8934be8d15245c42e882fb05d85305938de2077cb0e54670f647cd10bf606c98d0b35d0de93ec28fd5202823706cd3bc51fc9d00b6d13d7d94f6991ab77cd6d085d724059bca53c9c96288a65a6662edb3053e85963b3f8644a0564801814b8920a2470be15bf651c04c967c9cbedc0c03385c1550526bbd37691bb57aeef35096c1a4efbb7b29635d56bd9f86a746eb70f0640ebf17902f9508d8c40be744b7939a80bdf38c0507189344cedd299f4299d6e47946c5943bcd46c6c8e96a27d673a92feb743fc26cccc89ec415cc0857cb3aed15cf3d2868716f13a6268176ac540fa5b61c659bda78b7e4b192b57e8ce1a6e8ea4cc66c1790cc2e26ec21577411ef4b6452d4b3d33e31a3100c3be2f04ec19f034d005f9237c0cc80f3029925ab0b8684d5555f5be48876258bd6825235c18d2fce326c6c1784c0cc05a6b674af317e86f3dba8030860608f33607a08316e67a2d689622230719577369d94cd12d111b5f3c0c69569c1722dc3a0ecbaedbac7d1b4ae0dc958c62f28aad554920536ae125189bfb908538dfc1979abc495c2bb5485b7457c34b740023c22dda52b160f8534a7ad78e6a013df659838866803492a3ddb419876fbd6ca6c22b018bb11099b2309a8ceb187f0ade8497cef6ca94f467012d885ffdeafdc4e545ce2447dc8558743fdceb7693032ac7f4b3d6d8ad96e6de6ac91e7dbd61ca6a9e27bcfa55d26e6f7b7f0d113406605f37a29269b3433cd344c611f75d5e3ff5e14c44416cd106d7cdcfbdb3eaf1dc0c1c38c08e80196b817e41b9907f6bc1ff55a8f66380e86b2f9314da79839182fe0befa772d52b9710e04a32f846538fe9a76e836ceae18d224b41ab02262ee97e1e87c49f12bdd6e7228811b6046cd2e2796a8f748d8f91b9f13143512ad78cf3d5b969ce1e98c440ffb5fc26c2a627d08f00b32b0b34866d164d97e8614d805e7579dbe060a76496ac49d30a1b68179e05868a8a7022c4e63740d22b9564d5f0ac3f845a78fe947a20374e71b683433e960ef70d14a14dea7088a6f86f5430cefc7b307881e1576d40c765272b293f914a872492f56d239b9f18f39a4faf2973fbb98420777db576d778751baf4f58445be926862d826c27523aaf904a647d9112cacb90cf34f43165edae6afff410a825ffd6a7ecce85d5e1960ced819f6e7d0fd7e3aac42c89038a2090bfc95a6b5872ddb2e7f9bf68b71b81c1a7651e225361f0fccc5308f2755b0a38b0c5ef2ed8c96b98abf2eba8e5b36510481f96c1824660b83f5c6eedff13464422fd8e72ecf5c344f42ff07e3661d91eeddec7f641ade2bb2f41d74a522d64ad0a085c6a6d8283d55e414f9cbafbde423728491098938c1c39f48924c3508a5e37bc5bc400b65960b521580f00e920d18a0a1c3cf2bda02f58edf01ec80eba2be54bf1ad9ff3bfc2e1234733c45ecdcc6381b250a879d768296c2ff31d40cded6c9a163e35184174df29cd41615bf3c8f0321fc73f8d7c05c5d93e062443c2ac470aaddcefed66906a31e6fa571c465a86a1982c1238efc183a547ad5db8c4027473cb12dcbe89abec70029a8518ce90f672dc7062e3e6f56b6c2ce681bb5e58e5df0c397e4b1c995ec18cf9f768b6e53261fa94f97715602bc0639822c4795d076bb957327b17de77ec7a2b5c0c2b87c6852d36f519009d72ea3ebbdb0aacbaf98a37103c3882dd44f1c2718a4274bfe7f9c9d1a2a34162adb6b43e2d5fa62d03b8e712f2b3f889112ff7371e686867c561c548b4de07715c6f9814f552cd6a0943c364a37eb0d75a4cb8b5e0c17f2345cbcc7325692eca54c085059665b05c0603cbf80063b1f3d9c8d03c990480f062e5bc8a7aac5371867f445580b10b1b558973d1edb05e8948515956854c9de6266a6bda5fca4a3f9959dd804efbd6215acb918a9cac9d21a4194fbc2dac38c821501597304ef0c0394841a67dc257abd43a6d4ad108c01eab5c7efdfecb03711ed5328b7534b32c621db671cc42d15fcb2cf1492bbcb03190c6b8716dd1033dd2bc8765dcd86fafd15b1077e20a4319462310e0799059b6faeec8bfdf6088b39f6105602f3a9f3eabb6d14b8a2cc31ab7bb4c1a6e799dbee7bf98b49217a6068675b0725cc0c1474542d27411b02ae17b1f8e28ff979473eba20cc82c727c979b2b5d78c1d51fdeff6e0e46d218251599c15899b10f3994c6cbca8e4302a324e1f8e69d48ef16c581a0de0f0463a27bc84e871d5374d6eec8b816629c258026a6f22fc52322e1e41208d9dccfbde60fb9a379e1adbdd36f57b88cee6042e07f9dd53006a1af5310fc5c099f886a50238c61e287df30bfc432ecad37fbb113b2eb9936fc6703f8d8e084dda90023b87685aa3dae550cdad3375040ecc19ec69c46fdf929e63e05367443f3bceeed21120389a1a2a79e853d52b06fa196b0153165a20d363ae4fb6a0a38cb6f655ed4b34420fc7cbff100ba8c9ed865f32c930f37b2c14c12ad42f166443658e79824c6e3dc8c4d6220c00d9bd0c8e22f7b7916654c2b1f528122f83cc1bbc3030b17e9c7a0e432f9e06672a6c48befc603d7122f2d9d19c2c786e95f1acaca592ed0f67e9fa77b08467ed8217481e1597e6852eed75310beb865244b1af0707955f2c5c06e46247c0cf6ffd6d7f80e41642b9999cf7fa218c698c6e968270c55e745e5ff4a20ec0331041fb5bbd68723443b1b9926469d79ad9948fbb0165ee1c22e0e430aa92d7af794b2f4cb32b24213b418c6b5e7c7b8fe84c22469b2fa0b9ea09bdb8bf7eebf4aad2ca1ec21549e84b44082c8ba12a502a21ae94769e296e78f9368cfdac80f0e4e9b13b7c94dba5015b6cfc24d4a91e1080ddea35b32a6cc4a4d395d0e2f35b602227dff9707a9df7a80c3ba3a1d8d58e27600491ea66180a6b08fe621c3816291c147a9a274a4b83da92f6fb83c0ab6d0ef1606744a9f2e6ae5d470f829c01ef189e61598489b0b30c93f16628537e1b36413530446a85451f8df9d0409936d219e8423bc2ff02ab4a1e78b027e90b50a339219cad82bd54158fa0b6870802182ec13a82d9f3d9761a546232b0a677d8aa29ee55dbd312e91a31c4980ac098a930fba0cdb397980b9725597e5205feb08d913c7e8e96ac3f4ae382de170bb6f6b1d9d8e37b5e31b8352d0117246f160fdb8847d91850de543ac84c4f3d80ddc9fad385f72579a7e9462eed90c63c12bb10a072f9750c7b187693c9f063bd81669edc183f3c9b0aa4ff016bf867938ca78fc556a5221329176783052443bb947581fdbbf41d3f6a1ec0c3447a277ae14efdbf69ad4551458d86c5cd9e633be72e94deaf7c504ec64132012dce4a6d1a6a0d917df4eee155a796196fa50c9dc080f6436300b383759ca67de734bc54772e59c592a0444754a999d2561b19cbf6280dfb1682b5061de17ffd4e150031a66fddf0ec9d23105e54222433dc2795a6496e2b2ace303cbdcd4eacdce7cc2a4b143d403f261664be4ee29b31a212cdd33d08cdb765abb0f2e58f5399533cb074b1ba4e7228b7e26f00c63f18023186dc727e32dff97518ded73690d3f17d6127428dc2797ad38de1c1b82c6634bf998a21d47642ef74977eb633398a380fc540282badab46360c2b40905b664ac10b5fd62d5f8905fa61b8b290ca74078d45dc70131d0b9c83461c76e063e28573131273feb7678788c2ebf3b2aa08159e3d36c2489a5f5a148ee8791ddaba2ca434b7dc79d77e36799d059d16513864b304d69cba3bf241e0b4103a1716261399bba4f372080256239626c3c689ae1d73e23bd9788a1ebb31edccc2919464915beb9e215abdebbadd28756454a96a1b5e144b601fc9659bb8841179bd7be25fe227bb2f06206f8f77b7cb8996d1a2c535d718ca504068884d644093975c5e9d02f12f260641aed9ffa27e3d510c09a28c4d463df9cbf9fd6204355ec35bbcedff2847b56f90042b5d2259538246635c55b5f442bf61af979fcdcfc44d46fa2c6fe72c6af2f344a2ad2384caf8802f0432524e9356132dac085630208035175841dbc01068a6c4d000f703507aea4c35a589a62352c53a00f46ccbf8ae59f30325899d7048489c5929682eb48e388a7674ac777aacc007204f0f3c7a8956a5c236ecda2ec2495b5b263aa78c0c687faa3e8da6a4a5d539fee7632923dab1959531672d2a70bcda08ba01efbba10d190508e4d9f883188da0bb6ed01320e2c02cae0cae375dd705349f302ca90a695a4866cd7eed58797f94a06fcdbc26d8832d88d89677a051253b6c1df6c882d2543ed550fd08f7c7434308aaa7da649ec05665294904fdd4578a258ba0a119d10a89b5da274e74f708a781f3e9e9c1e9b41b9f84717dd8992947074ab1dc2bfe8459ec340fa9e1165a77b51e6b3f0a19c34722fedf6237750948040fe126e9ff0d50f14439165463da56fe144345623434c56048baa1ae81e1a463682362ccf14faafd3e28762601f8a98a9fd3bec163c569ed79ebe4e5f4433a21dc6ed4bdd2c3ca6f90e773871a42c58dd94f66aa0fb69905e8eb6e08857e166814471add392270bc69aa57101136ca2e0121ee1ded488f01a8cd2180c11ed4159c06361315df7927e9fb20a5c815633a05f936aa627ef4e780a47a3fd2877bf81d323433f1a7233b6bb8262524c46273e897a3c8be3fb71214a9c208120177dffa0865fa29f2e347269fb02b1fd1da71680c58d06ba08db27ed90a7ae40d060824b8f55aef7118eea0c1497f168243c6c6ea4105d298a5e8ee016bf2af062da3dd102a65a9053a14dbc84a03570561b0418436f76d15d95470a43db5a8e50d15b2bc5420483671f727bd5f5fa9e68b9099dd1249425b3c5c358f7af19663cd9a020caf9662e6cc73b40709efdaa4bf911c470569b362a248e18020872a5c37d090a1a327a3f21961c2c605374c2a0de9be3f28da73d6736147f99213941f72a7b8e2a18ae6b43f13f29527bc38ebae74aab3d010a0d9171e7e6cba2786e7ed58eea2033610cb11652ed9ecad016745fc84c6e119006801abadcb6921746b28d5f3aedc41e94496bf61a971dfc1c95b6e7674f47f80e09a87985354a003fe3b650590aa8434b80772deacca8ee7a649ff22de2f288be7b51992373433f69a22afb7b8172212dba7e15f364a2ce03a3cbf90f03f689d21f727223fae286bdf7a8538ce8f18289cec07b6874b41e388229c967c777684515de8bfb37aedc884476fa87ab8283c1c8d9491d9a3cf1285a56280b5c01853fdd0ba6f9ef9c902f0e26fa9e92d6eb44b1c59bc7ab93caeef89a0330e0157dbbdcfa9254492d8c853a6a113df350ec459c35370b2a574591c81ed7ada50592515d459b29590cda9db15a2375289d9317e2b4ccf324bd522de794423f83ff50365a6409d2329d99bf1bbff745ffbaf6d21e60908acc987be61eeed9d887b8267a708de7d89d588bc5318025baffa7c255b2cc271ea8342a17c75a6a6f73feabe40f7ec7083289f86fac99922da8cbd066041083abfd57608ccdb2462e5b89cf9f3ee5a8162fb3e33cd8eaeb1f45f8494179035e03c77dad1f2389bfc708b64858e7688e802ae1425151e5a8bd1b7630c904323a599de93075d8632c2e7d9f41f476b68c09cf8845b574b5740ef46156f10891ad4cfce778a05ae16ff67f0dd7cb8d84a1d1deb7c0de2fca06b6789798c536e58afb836a4de6acf4b2bb7c8e5117169e7a1e2f142677b2d22e1fa47ee128a921ad95130a110c3af7c49f07082c85997d0a4fcb594426498399e8491f43117167320f3a8f766b844c3727d9c13ce33ac780354dc38a8646222ba0da930761a6882a814cf45bd6d3bf4ec937717cccb125a800b76c1937e60ada63ba4c7ca012c6e5e8fc538db572900b59e7bc97d98ed277f0a9bb34868d4f6d66b24f1e99bc6917a27b9cd7e5b341396592177f751b03ae239dc699af8f2d4a39ac42145877e91c1f0ee7a9c8d1f91c4ddf4f2e7e5e22e4c0c8eac460db60d03d1da136608014af44139ce112d06236131eb846e954db4f450d4aa7b6240a3b692eaed5bcb2e2684d57b2279343a45400be3220dfdb420e6bb9f0d6b88c774795695e70d7dc114964bfb079a37f65f386a915566cdf18515d5f56129ed2232862438d04c4b8593055edda2558c8676359c74b195ee36a2a7d968d2ff634d007cade3b81436ea3d74a99b77686e41b53be0cc466d6e14f04b4397dcc9bbc755564d3c3db754ace740571c0a6ffee3d7927ca72293901b8185d6561a59a014f56df9ed6138d67f442f6501128c37a9cd8883402ddcb8c07fa8b0b3c601ebf253b66705690580f5d50f2b13b4f0784bd3724093e367a4695d8e115e135dc880ed503219c1dc8b16283fd9003b4926fcc32f670414f235cd18906a9f454b69b2a6c58a180dce204121481bfe63a7b0b210b5604bb903fb965d1a04b8503c23d0a39260539f256ca409eb61f14a584f0310209ca3affa0b20967b3a66c4806e4414212e5eb0256c06818001273a441c20585c874baa65c0ac81d5b3041ca8beebf6f89e74f456e9a1113f415e87e0aa79a25fd861142152a8dca84eaf6408aa41491668c00c223f0d88cce2dc02248493e73fc419b8620e326a05f6e90cb76ae5f99b19cb9b9ac974d8b7a92ea570e7741eaad4450f17aa7f4622d9f519fe86a95dd0e8fffbbe7f079ec08997ba56ca2feb671ed8b5d45f687c65645977770ee4f901edb0edd0c6ad2cea1e2ad3b1ca9165698a0f4c88587520bdfea2055589378e95901ebc1d1fc1712572efbe107b3c14dfcc692da23007a1d85cea4b50bcdda5aa51700d6771cf505dd8e64c4f53b25a3cddd8db02dd4f01e044ee99b2e59a44c7d5aff464205144cf60b3a71b41b45edfc8eed786d32ee67cfb2e89f08c39045148cac6c990da90deeb9da4ddf1e4942e601e5d8db1b961777fa0bf78e8f750938ee2c16d60a3c3ed806cb7b0f54208f36cd210423def3094175955c8203f16a7bda8e64326ee227f4572f48729b1ccb9581916a43884b4b45cc7c68ad26550e668036d7546b91a08d22bb48df29e2c7a672f24812de604ac9a43da3aa66055c2b2f73871c3f019cb50e71a44c1d05b6daeb156f70af500e1d4620d384ca0fa792ff13bc234c4bb6a302ec48afdf088b6a3067ee7b4241a9b28db1af824a63e2f60a48c390acbb5e9fd49cf69a36a8b89d54029f9dafff1fb30aa177c4ee700b91adfb2942e9eeffcdcd2a63a5c2a876a5e7e147a43f3c91bcb10d3df9255af5f578cc35656122bffcc62146899406e62ddbfaa717166b45c8be6bed4b6cc08724f1d015bb28aa998f87b4720e6881082e6804d345109329d10166cbce231d83a8c05c28a45a5672f8cbe5a1b44430ebd69dafa9c4e4c270dee8b6b7eff3475ff808201c497c080c4edd0b37ef4d6b2b0017fb59909e3c42c79066b7a35e0597ba58039071f412537132bdf77a8cd0a0ef809bcf3074694ec62ff748cbe0d00109ff7bc022d6fbb47200c4a9c043a239e49e3d595745b5be26657dc5339b0071ef719984bee5fe80979c04c373981c2ea80086d0d5ec77f8c6a00a06cfc177582aa435ad1e019637cd06ff5ecdd19805abec100a577091185d9fe0ca51e354cdca922af45f050cb8df985e20e89146f55a161056c4c16d088dc35f06f992f626fe32fd9b3626879b9f8540cf1fb705fadd0339b12411d24f7ca897f7f481cf0c19f38e32868f61743fea76b61f7ba17c77f6fabef026688677496e81e908faee0788fcc5b44a04fc329e2041b53af9e5b121b1b3a3e164312e44fec6008ad12c2a6da802ee853f02051bc4a6e46b15e0927c41fc53a2203d306962f168837bf0a44bc0c5f0746ca2e5e2b98f866609019d6c91af5716349fd026d2e3298915c36bf59e5a5a23c0d3da19230951177775d7feecf074f07088d9cf7be0d4bed51d6cc4904117771242d2c55f2891f28fa054ab5292ab1d9a24ed2b60b2f96c9d07a4248235b7fee8dbdcaa507ee87b05051fa44b216ab7bccbb8a098d554a67bb1450298caefb3404f65489726253064e0424d6faa5338ed78f6ec97145ed035257fe0cc2f389148b326937a17952da36f41b33d3aa89607d8aae51e9ac31ac1b32b7bd8c78e5c925f886e2974d4895057cd0616a4c28426faa2ac7d01ae8e9f226b94bf253b035ff5d30a65a4986d91af8f60b696f945c5517daadd565c0b3a5b45b084848c5601c27086546c4a65949282c6e8c649fd0ebdedd12ddc3dac2fe15ad952dff272d08bb058c9181a11a1e2c45c2298a42f081926faf9a4acad2c0d38113568ef9a78858d9ed266c9eca6fbbf7dd04e5417ddb1aad1e2d875e509a4e9facc096366f65fcc37a366356458127d83863a24faa80e29343db0857c2014e59eb2fc1120b8cb4e09db332dc004bade517b2a575fe657ba564d6270e7c8dcda27d178c1c18395efac9255c37b122c3fdce9caee38c96c7bb6e57c2d70db3b1b353e144487deefdbc214f2e89032e30a96f410fe1dd22943d5e3c388158dd0deb57d0e29d350e53ab256b701ebbd838e37a97c51483a5d20a2204f037c0988208e1f8676780089da7ed0aa082f88d8871688f6292be3d2288dc178bc95058f94fc0fab84275e406c6be0ac0b62b7d2ae45ce461244f1f15aa16c20a0245d4b7ea442b1743930ccead6876d95eafb5440d5b56156a6888a91a0c59c7c2dcba734c2628c4d598e6479c013565e348ab79e33f816a0d551b1bd015aec35307382cf041e4a8350741f01a0345f88f554e5aa2daed7eaecbfed2fd319d8355e4a7e7155c15cd0aba187d79a8184a4f32ce9c81cad7c91d926859518903904e5fd5a4cbc7de8ea2d5f3c8cf0b7cf41cf92f18b4ed1e24e7d3261f14af7c2381b394e375a14f51ebef1c7b891121a6ffcc56d844add98ee6159691bf1816d1c45374e431a5be533c5df340fa0d95353bffc7094497ef1acb843b82aa2a6af5ca5839e3e8d877ece3289deab088fa3b6d26ed8d22d0674b45471762ebe3eb48e1071f1852ce09a84944e48987e0dabc8fac4febcfffa4a252da1e9c325cd47a53e2d21bf48ae8528a07b0a0921443e5acb83add1841675c2e6194d2885423a4a619b1270d5f00bf9159831592944fb256212982f8d4e8aac3a760e81955b0557be904f8af03ab1f3948e66ae88044244451c889b9271ea944d9c489063d57462c5b901174dff0aa29f4fb5dcf4cd5ed908c320ffea6bd88d31d5a2f1aeea17fce98936f10b7c21108d0be7a769afb7142b32525014ec2c504aa01bc44c185f29a73f29a04d86d99919099c7e757d210bb8b8c895c351b09c74b420a4a629b9f4917bdbc1b429a74094e65b4e61fd614a1e4f9a0bca3068c196d9173c96569c2e73035b9c2176f681d4a92c1c16cbc33104acc05436c3008fa641208c008fd61b1ba5c29d3b7954ee599f631978d30d6c74721e43460390844b0d50710c1de26a12a09072096be9f0e208fcd021db857aeb0b0f2b9c7b2ddd05b139076bda48248ed58822404e40e272c1ce2e5d18bb8e9b92870f911260c1928ef1796333f06e7c40cac95dfffd17ac00192cdc951befc597db677a1fdc36082c566c01133835863baa23f2990a71a4b997e2d144ee93b0a91875a59392d3c54b0e87b17b5de427b2d93464f3c283a65aa4a25952b9fc553b12ccd8e9172eb70e690f3fa222de3d28739dba43c3e79253b66887ac4f9f4a09aae9b62c7f309079c2b60ba11fb4c116461a0596b945cc7282d00caf7322efce75f9e757da271b9a5dd40a083bd185df84e635589690a595e5fd4772cd8fa313958aea8a28fdd0c2cd69a30d1876a01302735f2263ae5b905805fcc138afebe58c1542060aab4f8d3fad345d6c5b24ac37dbbf111101ec0814c3e56da82ac1561fd41ddc52f84443004bdf61c5ec21596ed059be17cfdf30b030cf31076558b91dcc6183e01e4eb299419df8745a03dc3468cd90dbfe2089f3d0759048710ecc987b94f2fa01b068f6004531902d3c4970abdcda0949a90c8deda60bc8de05f173924fb3f092f81e990825bd0c8e572972e1dc1874eb6889a1451afd3a4962b11a682b6a9f2f3abdff3e28e04e93eb90082c18ac882a8f08782b80d41284027bec42c9815f5793b32792ae6aeb2950b6f678820880322122cfcfb80d2004f0b10c364a8139e27dbe6a1c7ccc1e133468e92d70f372e0c2406c2448a8a79b7f03a58ea8911ec2445d4e48f049157f1f73760f1540e5ae1bb528df6e1b9c5722abce3037ab875b7016a6955f05fb08af0a02f493579b5662c9973115c8b8fef596d92a1ebda44957c35c4e5df1743f2f30e9fb81e67e6045cdc8776c3aec3c2f82399d96e853e1939004c684dd8840b6cba8120f40d7d87c8b4bdf597b730940652cd4ae2d9317a971c0fe8f007923c0d33f9e9b1613dea80236c6d3f8a0e2fa20478847c934d7b5d1cf8bf74001b368e6a5c33f22737e5f7f72c7664ed9783698b9d6b7efeab0bf226c74f0a71ac2c4b36452eef74426f1f013bae8e18ea99eb2a7d659aa1500cc8262050ee117d899952ddaf07a2a5481edc903b1e08193337c35fbfb1de16c9432cab419eb4439cd2d3a50b063dc0bccb370843b385d87e6f2da5c2428c996cc69344a89efd5eb61cb82083312d84076aa26078c3ed1fedf4b81689b023d4eac17a0f557192df61b6a53384cefc4c3397cdf2317dd6d9454dd6614c1e37c7567618e3835f55e7e3633b6feb348b8fe33c7dabd4968be894edee345e4f9522d4d18bfdedc77cbb5665d33f4905adf6be7d141662ce9286cb7da0daab97c76eb1e7fb9c84f06945188579d13a1e3194201d718bd890bf9599715309fef4e27e7179701de72ff442fb9a459b561c14be893d2c845f060870646d1587d3d2b08f0707b2908d8604cd925923a12d02993c9ce7e5f85839674ef2c986d6e654d3a762647f94c50f36da6765f4067e09874e8a877d823c308a44a04b0afe64dbe64bac3431e2f93192e2055a378e0bb80a4c001b21f7463cf5c95aad90e2228c8f908db455b40bd2d374cddf2f7a6599163dfbd5963fef1ded16002494e1ba3c9fed023897cde1519b0590413bbdb7660ca452ce9c13bf06a3a3008d9329714be932c5b84ac3c2601ec13ddc29aa0ef7c6f924cb40f84fc02f744ecdff09fdd86c119bbc678161dbe617c3fe4bd80e50ed6c36a463a20264f5a98537fc427a7c4c77c5402786d89473099ade2cee4388377f991b981b9041738c405e7ff58259d2c9a7123b403185aaa83eec41ab703a0d52b24d9cab539528a7267dd597c01d6e1146fd67473c5291f6e3de64306b15a258abe4137d8d44c013cd6888bb74ec5da31383c72dfbcdef3cbe36b655cbda74676f940bb9769f8b85656030813ceaa3489de2a8b8b5c7086549c566b55aec8d7d97055659c65abf2c8e0fc455695872702c8065c1236ed243eadf29a31d994b97bddae79a6f239f4f3145234d33e68768bd4606b09a31e7ea835e7451caeec77492201ceb6589bf13e0243eb8f3f8f4a90962ce5ce189c50f473e4545b91f9570e2ee1508991ad920b9a3b6db2322e5dd787a7325b67ab849ccf4aca3fa26a124f9dcea59c4649608e12e378c2e1981d449143bbc2c06ce1d4ce53deade73b4082df58cbe0a198a0aa39efe57956b2d1b9f66aa6ee4f04d2c590c7b4e4a3f1ac8f0505b1c994add644de618eb18ec5a5376e91a4c4bdf3a8e19187b66483fe63859d4637d304635db9597609a2437f28519ca49c1145c4986a99f55d8f1261d9495d48485c2c30c97e2a4652e6e0954c46660f6804e8f0471baeca2eec71e63e93000a1764c4a3c040b9f39c82294b801d56287a8f843b5cf4bfbc2b70fca2e7a00567ce9608e6767cfa782db60905d581f3ce447f77cd596ce2017d18114319e8358a425134e77afe9186286073d779322082f633f420bd866873d4fe9eb19d4215d1bf119b50e65b67aeabe49ed29dc05a2fed0890c042a42f561dcf4b893da44b3576221b0ef2aa911038068c3565ce303ce94c7111739c42ea431a2a529cd4df23d7ecfa69bf6fda218be80f05b26fd00d13bd12f2d4897e3df06431ceaa56335c68d02bf7de39c24b052f67c7240c126983d6c254f9ced702b7f02418e651e5012189557226a627ed16a899341ab2381529de574a16bf6aa1f5b167899b39310eaae45264dc0d77461f20f093bd455c182a1d70532f64990da3dc0b9e7683597981336e9006c930f5b615a08fc3543ec03205691c06ae086c8100c60e08a526e102105a48f7f69b7b2fd30731d128ed2225a3ae58e5bb638e8949580d3406a8f2a5aea5910c32514cad377c593c69be8314eafdd311cf6a0f525f12cf41ecc94ec3e70db21c5bf23a4f817caf08edbebcc6c6c16b5015040fb39cddfa5847683e3f723d680922e7b1f024c4580edcc4f89a75bfd4d0c11e08d9a0953b519bebf16688dcb29ac1561e6cc1507247e7acaa1c9d91be395b269f03b906839cec1945977a834482b26c3705fafe3498d8a50f1055027f60c89b5b3649c5dd074faa4d6dd197a6564b6055b72f3c842d28b881e1fc76ac3b6c11e9e51ba04592ffca86b3eef2ec0bd23bfa1307bd5e844c45ede73a65fb87db96a0ae05ef363eb5d661ce9d027bc5e89457a66bd457250e9492e4e86fb89d0f137de0556179c203a24ec0e19f1156a578829744350c87a582cea4cd2c0e778ff44bce112cd17c49edb7381804427371f0d2492d69aa39a1ed431fd41addad86fd70ecf6b7c2f20744bbc2f9896620883bec68494b8b6eede4e8cd338b31fa907949e8d5d02205b7860800ac3c5904bbccfba9d19ce7eed9ff64abbfe17285350eb7fbba4dc67555c41ccdb5dc9941eec72e78108996212225186283350e15798f4b06651461520c7123c86853fa229b448d16808820d972d0362240b8e9b23a36bb1422f1d024670b40f2e526b472d958b943452053ce94346cb79a5dc368666f669d5becad046e516ff20ee1599939840fe6beab9db66d5a3bf3318f3d363cbb6a70ab7ed5ac232330f0cf992cd75ddd82991540e4d01634f70853192f90ece149425a863adecc4c683253ee7693310c5f12111f0b7f164f0be013f172ff1f87811f15cce4d4317098f99abf5c5a228c56ecd6fa580a50b857ebf7fc28d2cf6d77457597f1480014457d484e270cee270572d7e69f939f94dc7fcc87e19c777d08800ce611f7d840ccdcc0594877179e368e90425783cb91c950eb31a5deaa245f663c59e0dd77dc0b3cd763920f66d6c5cf332a25cdbd41d87b150f1d12686c5100129f0d0ccf3649d9a81064c2ad714bd56865b8b3490b46aa6e116d08de3d2059d07c5071e7bc0f2eac3dbf9ca595a8030304ccb64ac907733a5e7899e8f090443af9af1db5bb241d2b39010727b06799cbfc17999ca786acc443eb331e983fb1528ad7b1389834374973be1015cc08199e3f68c8fd9bb00f767fede5cabd9883babce5c4e7f571b597cbf0fdd33fc6a2ba542c6dcb3b66a62c2c6fd08af88601cab16217314f348976228a1e014255ea46c9e85dacf9e3376322315524d8bf65c706b34b975dd723dedc7f331f5610e0190d34f3777961f3d89ff2e46f8a4ae545bc059e8659daf1fc5c18108c60153e832a19c3ff96fc212d95fb68ac759154fcb43610a5eafb51da4776ab83fc0710db5b0212b90d61f5881864e9f24a68298c4eae2aa9f69b18300380070210aebbccaf89b73f61e0cb3d397a895c1773d7941680790bd1239d6e4151bc0c5b97100031b817b3f3930ef444cde816208eef6276e5c5eb3dbe779f534e0e04f5e1f422af9a9258876e952a9fac954c20185c3f4bbe396d195b18915080254509e680fd59d94320a99b941a6251c1fe8c22ad9cd293c3e72e8333e24b8217af7da1100cab8c0acf22328c3fa57292c43bfcef2b29f2a62ec4ad3e913d51c4e62f3eb9dc518c8b2af7b8787a2a8106d18a736a1192cb268e984d4c511f79ae2d187f886205f1e72a3f6bdedb7c7f41e5eccdb40495838777aa465034d1435164713b38f36644ef23ab828d6bbe9da6c8e46f70b07a1c17fb2d61de2efdde9e43a72ec7eca7f224f529c7755433f04c9a06f5a8aa699bc45addc4dd71926958d590eacdd950306e0f6f6503901ea31b3de1b0bd7e2c853b52c1afce2250590f32b3da70bcd162d2bdad0f0d8e46ce150feb300ac9f8d2b7d2d60d0fa3e5686a8cab9ba8e8502328df1ecaed17f1d2f50af5b29425d50e17919fd552f53cdb9d2abf068c43575c6c1e12a158160596cb7fa1b2627fc95fe9a323e320dab5306ae4d035e01cae3b4ddb9a217c59d479066e8f5791f084e17798753f498bca161f48bb937cbe2443366424360b6e928ecbe99b8657ad86af7533adbd5713f682088e072de72327ae650c2cd6efac5871d61057e89174b381de9168faff3e59b4bd4a61a7874b33a091f68d12bb24fd17ec840a867f6a8b7e96244446b44fed1030621b4c38ec4fd05ba35720f797f37227b6de1e7b60528409c16e98bf945f64745a301a363e94416c34a2e710e6efff7c7cd67541c7bcc36adf699de0198bd9121e3a366cf410d8890d4859013712f4096d2b4fbf44bef516771befa0bcd1811dd2f3f751ece82be2dc8edb3d70852cc61c77c269daa1970c949bd85872b2d8bbcdf61e70a74ff450c6accb50e17c2cab8bcdba8e03cf207a69f11df6b4bf34d2156d8b521e6229f0cd73737aaa97eecfc1d98a9cdf91b7cdfed8bb3264d2e47853d3834b278b20b8809b10002d3620b835a4453024d4fdd6bd1b3d1b34f694f6bf120e699460e96409d75332057646e51daddba53b8a4a3facc4f705470774815bd2fb95d86e290c59904d36d0cef24c3ebe4acfc57a4a6fce3347ff9b49fa7a69db3beddcbdd6ce3ae3afaf3bb01f271dd3d5ac859717f0e5988468aecdfbb11f3a106452661400d659d4cf33e9fc89110e353c4b7ed30dea4465b02b7c2ac819f182ab3f19cc49802c384358f68580c8f76d2159684957f4f180d82e1c89ff43b046f6cb860937c8fc7be58cbd62feae57522ce6d94351617bfa5da6aa0327678bf81e7bd9896f14f1b9bdcab41776a94b4ba32087b5293fd26f06daabf2e9ea3d6e7a33ffff255b974ca955b1ab10d9be90b6816f18f8c92e747e7b3fa22641b23dd437ae9a3c7c995391eafd3cb1e5794f6386d94e94011653bb6c298638c7eeccc8b95c538d7a8f6319d592c06272cd6d121a44e5b21a7678b10e6955d2f5746d74f8a60b0d494f76ebee87bff88febd74c801cc06ba3b88be316cc0a7f2e88c322ca6ca3bb49520cb4f94fef7a41f346ec7f83ee375300a01ba81ef6f8da14f06d9ff4f8c4a143a16d727d4c0219d3c7692901963caea4f9ddf39a208b5bff660507f366a3963eb97f5e3138cbe017350e59e58f936548204a8afaacb8843459263f34162a260d722ea833283651309a6720c233c88ba333122cf449dc50a45a20181504701070511393c7195be9be487ac0fb3f384b09f2e490f9677277c55264742d509551b39130c7f661bc1dfe211660dc6cb3347c125c4c73d244af5747ffa761ca50241b8816b2c3f589a93bf2139f0b4ecf8f5e5fff1ff00d8c639db58a507beab1ca3351af3a16a20ea3352b1682535decba9a19e3a26f4b76084b51505c365d45722dff850de8083217aafc1cb578cec3624eba28f881841a506969bc7d07113719efd91bccda70716ec9e98ef01401d2a37c5978eb4c38898922b9f9dfe597084f2f53951afd0c5ca1a9233150e5abb17dafc4f83b68f82f3838464661f6603c16e1f1386452f92e0ffddc19a31aae1ffc6502a00af83a722fd40da46f9461e048604b20ea8b5443e8618ca2304d010bd1f02a12806917ffa9c07c3150542525b63a9564afa37636019c021ec6de2966645dd6250e6ff803958ece0ff7d39bd6841b5eca6e69bb1ec22c753d7f43f00a3726441a21d9597cd9220a972992ab2851e732bd99fa375259d1261d76254b471a7d17b9a449044e39943848d8c153ecfa476f1d59d362eaf30cc3bd5e988f26a2a345274e55123e2f596f4648a129f78ce14bf68d411fc981cc983a89d418cc98fe777a91fb50f2d9be48de5eb7095981070f6b891180a363d61abefdef8812cf89009631a8b452bf07806353993a478ee078ab879a40b8abc652bdbcb2ff267121f946d24298e94a09d17925fe10879a2bc7e16ae62748c069722a0aac10854370f24fbaeb551e7c10829c96dffefacafa2a62846a663eb8faa5ff021aba4ed44e1fa4608cb77ad705625f2edcf335dd8c56b8cfaa10349404480ed1079e6c7030f958162a51877bac2bd8bbe7d09d9dcd27870ea8e6972fccc15d24aef238d04dc059fabafba265c5964fc83040188cf369809d1566ee96844911ceba812f6632d3a281253f988ec0505d58d537694efa8ab382d724d84d3799e3904fe81856e84f4288eb7a073f13f4d53afaa68a3005dfa4af55139adaec4729e563d8b6dbdbc105f099ae744c357961af17c0db3110b92419db1446e680ff6e43617e8e00f96aa845a8f9b72befdd67bb2646962f259b167cbe8c07f16022a662c5aba965fbd614b2810aed808e300cb8bc01a8a5797b9871f2fa7a0606db9016e12d538724881bb0ccd0ff3e10aaf741e9bfd18920612ba8ac4e0061a2cfe0787b6f4068059cef5d8cb55833b1a84571509ad2db7856348e8f8313324ae21e5aeff9dd49a3a968f468dc9538767be9aa4b1863548a95ada2579a73846a1e7303c231076e260447b8ddb1033b4c1545ea89dc3b7c858446168db07f5e2c0b88eb7186e3882aac607d562358328b50b3d64b2e12bbe9fbe7a381a307ecea3db8dc6b1d8e458e27b553dea31b1d3c60c1d649db94d38e1c87f1480ae5a9b0b7680d461f97a5a79baaa040de33dbbe5c23405f33715a18e1ce72b08b7d293542cefd5a38e45cb13343a98cdc5e76c1c3070445b828677744d4a08fbb160e0bcf76cfcea02dbb7345d3f92df42bfc61f55807bc006a307ca878a8c7c26c8f4df28379e2b1da71930dd24ab8eae5a762546397250f8bfb222a971fe17fb117cd425e78a3426d89b827bc16ae9b0caf4b6ecc03bb9e17a6df10ce275e59fe315d2c352a7886cb638d2ee6be479f2e6f20afc0199ceb8291e277d29aa9a13860cc84be8bd5aadc62417e7050d5bf9ed84fca73f7c2392481779c497d1227348648940a6754ff0969a679cf884c821a30182510f389a1359625f4fbc1b4ea876f90ffeff23fded7c2e20f88b3e52d906013da72aca0e61bd532224c3a65550c2434ed501bf26de889784b52e232ee716ef565782fb7e9f2008207fc0d4d90501345805215830855782ad185d27698fb02694ecde8c18dadf6c3439c101195de81ff633668cf91d764eb9fc7af00dd284fb94e3b1be92fc96d7ec41110362ddfc88f5560d2f91940919285cbf4372146e1567e5adc493d64a076cc92f2366ec4633a4350fa76ff7bbee20bb2d333cb8813807a5ccb76f1a6acc20aea104e651d403162f448017edf9b8348ec412522ecaf9400ac16d840790a5411cfdb977f871161d27e4243000cee87614e076040bb0422301ecdcf24eb9eaa5ab043af63469c5e1a76603ea9623ffee0df86ad5daf9776435798bb94aad2a89f0a1d431a2262691790fcc717fc8c3930adc820ed65f7d30f77c30f098d3b68fb2bdedfc0bb4d79bf611bfba7a4c9698b303088d5fe58dc54afd5a914964e974ba9514f9cfccf2e54de0c05fb4bf890e55bb34c1932995d923e2845e04381f3fe65ef5ae6b8764ede8a923f3ae4328c37256c2168876330669c626e10135864d237c3cb317e4273901cd4c8b8a765fc7b4c46b7e522d4a7cdec9194a21e3a7473e8f84dfe228b4490b9cb725ded47e985c4b80fd6d299f3d0832efca7308a14e682e46c0ac9acf6073d516968814522e1dbcb3685c50e5aa69153ad1d88763d6128ad44de019714c90fbd246ac8e8ed2336eb13d3ba61cc5de202dac63684bce669b073a7395085bca61cd7c2d881ea933136efbee219ee2ca20d5ced503b3e0961cde624313e23f5644c16fd582ddf7e3b169db5b97274f3b59ae2ecbca190f8c7d90540058079c859237d5c4a2f139c24e4886598fa9cf4cb9f6df3194ed6467718ca346bccd2783679ac79e110351ee04b429a5c07d06251f1f9be41311b51d39d8b532fafae0790c5a5ac6011aed06ed3c68cc598f0ef439b34e9c84d45c923d269f22816289554d350c0a0c9f6e38a0a0eb8492981b0b3f08c311eb2f395ad1f419f8837ba97eb1d664e2f0f8f6c1487a2d998ecfe6cdb25a1e2b7496177ccd8c706f65e5140bd3c9ee32557ba7ca3819f2b1b28dadad6e2a634858667338db045ec66144c6487199fad901c2ac8b1f10cfcd292f6314ec7ce98192b44233e16cc14818ba0cbfb70f6fb4b2293a0c57d69db494609e4b3cc26d3599055250d7db2c366a058a3912786df6f4ebd5f66a9a0be1c6888ee03b219dd41510041269a23289136ba819b19445295f5af7f85a4b9879d5587517b16caf66dc622d52295527801670d91e29cf77384b170e96f44ca70dcc0a49441372495f4099b0036b77d553ac0a4df8bb3125cae57e95c131d4d07c746f0ec0c7a744d881ec94f3d0453ea1a0557235234efb605785656c2e584dc4d4ac2188974b363a33271330140de601a97c9ee923928335777d5d6fe45cce10cdc6eccb3babfbbd17d2ae4d1dae227420a31d1d0743af5f7e8cfea6cdb4596e80df564ecc94eb8d7d7d01d1cd178b97adb97d35ecb2ff6a314a2dcce94740fab1bfcd1cba38573420e30231068251b29738dde480dd8745c08130b3863957788f74a46e9dac12d5bcc1513c8d48ea38888e7c73bdc7abab03d786a0a2d16608fa3d617c665989645a2c54cb8157d8def0c14709fbb06cfe41867746572dfd299b3f2500a825bdc519cac44f8992630deaabf8cf31367c8c8428c1cd935fc2b64f8c021fdd41f730a3b3f2b39976e99e4edffa1124c267bbabf5540b4c116adbf290e10f29d6304368a8fd1cb4c1953982a374b12a23f072cd843a01e6bd4b8609207cdb5c381b9230298d017ddfec228a310bd23b9d0fc37f0aa07b57b9f245d9008741eed2caaf711dc444e8b1999de4b136cc862dedeeb63082ae27a32fc27e55b447000300565f188fd9475789dbb906c1a7abe63b12b593388330016900d8caa5ea84d3724095f0de600ffeee1482a6b0558be5798ae260ec2af075eb07a7ebd7abcb31bc8ec4246bcebba0c6343e5e7fedf02e7f9f5ca2bd2eea855e9fbd3d7a532faaca01970224731d9b57caae0e3b3a8d12e16e8b179061791a3cc69bae0c42740afb4049d814044281667c018b31e67936027ae44778c6c7d7b37099f26564b24d431a0cdb2bee3f0c710ddef073cca22568448180df0ea75a9ad252a55514186b99493fa2e17f50788462afa18a8906dc301b8f04c2cc71e230b005fa7e89c5d43a3247086c4598824b3035845943bdbecade8737e63302286355d8513a515941496a32858f7df232260720486d622354c3d9433a407c9e1ae52d116721d0f6c7980b186496b0dcef0c559563f4c981cf157ccccd965648c850468c8d12d6704ce11dfac36d8592002ed3ad68a9aa1eefa0130f5784edb28ad93553e32417e171c42d50714c89d20aee0759c2c7ce483fc3d002de3cba8250e1107c364f0bd94f8458d22ec04e4f144b3601a10bef98518abd98b5399f5237140dc93792f4e5fdd2942cca545e8d516856a6fdb618e418d35b00e8048207438b5ea8905bbc0ce483e622f9edc1db9d9dae936ffb0364ac6c399ad6862bbe9a519d54967f0b4886b889dbc5a82929ef3862f5b824df31e0328a219bf806d7fb9337d4a6bc0c886539667d4ae38ffaebcfe540a773d702fde8e228f5a50a6a4e925a44e958f4bde14f25b5063be0bb36f6125401da006c67062a8134b70063047bfad1e366d5ce8e32f9ba462df156c58699c80101731d86bd8763ef5642de9faa7fcf7dec41c13fd0824ebc8c770125eb65fca93ebeb04dd7688951430d324627a2dc3edeff189a96c38c1b92805c00969d9888ade9034f183379044197258d4de257393ce9541a0986892ae45185f98b68954c8542e202ca9eb3279762c5ac00e6e7883d7a29191ac33be0de26840fb23e97686cba62fc312b4cbd16ec0547038c09debd33148a1d3c6fb8741535734a160ca715e4cd555ec39101ee7aaff4bd23a29da06fa8918f8ebee42a486c0422391480697d7092d3fb2c66ac9daa2b6e035164bbf29e407f815619e59e39a872e873f170434943c8046ac0e4bf30fbd3b14270ef2a0638599918a92e93d31fb1802e4b334a78c44067f00028a453863959c000ba091704ab7bd07fa7b1e0a0e84020902e8244fe560e2999c7a7920146972a9822c8d4cecc723e7445961b95be49e2b3cf7d0fa6b0875c3b6c8d57b9c156d65657cc9018d9c9ec286aba18ce26b4fd9f5a2cccc25e6bbf53a2b955f59fba3f7905ecd773a5bfbeced8dfc2a4ad93aa970574a412298568dc20fdfbda2ad9f5f2753d4f8988c2c96e1eeaccba564a8778e8ea4383e92b6682559adf0fe794e56900180e71add691c9202b7e6a7682b136e2af47096de09b4bc1f13ba46b502856f0709967e4c9c62ad88fe23aba10e6487a9f1f96749afa0c1f0a062a90cab54b095fd3b1d16583ee147419dc07d5534e5afdd33940a82a766ab14a2c025b73b60a44628c2d50822f82fe7556e7b2da8c248339db895c94378c0d96edafaa947e671ff9dcc2e8de25a572194ed52867c87975feae97e2fc661961487a0ec690dc0dcbe99a913ed6528a42e5717daa4f13b9611a1f1fdfcb822a5bb48e762ac7514934d95a3502c2a0874f9fd591266989b4d69e25ad8b852c8676d558200f49ed1b5cfba1cf9c31cb6db723e2e710d676a1a2c59be3a20a52ffb223199bb6764d60f1636f61fe02e7f2a43f0dff6fc5943172d37a8dc23bb5b7e93f50dbcb7509775086bf0284ac82f3911461d95de7e714e42b11dff3f6e4a28861360bc4d87d66163945de8c24a8e216568e3de8ca52c238a38b73a70442d9d8aad6887088f1598c10193e9c82cd59c3b5d019c661a5f02f17af6bf9400f256b63a75938309c130d19529a0710bb8669ca97ca7bb37d7bcfb58da7d5132f357650dc080e1b3eeed302aa31ad3ad52e5de796314ccba0cc3bf64b0d2fd9d7c618a4649b5da32dc6475f104f7a3e29fca4a2b826000ddecea892dce7c0a622853685da24ceb02bf168d49b428bf04564f48140b663cc4811889f6cd1fadd168402893f0fe6ef19d284246f207a5531f853c52b15b586033cf7d1cf8b887e6515589a474ae1f586531c171c9dabc621b3982a0193f5447bf1aa4c37b6f9f8fc95a0ea398404a5a46eec60f41017a66169200592d8ccc09ee7e0d20f5a75ce512c82fa6b8f8744085b7e1460c106786558479b7feca42c15e9c3a3abfcfd09d3d9b4ae2ac0f7f4cc79fe92264fdb1f0aab30e36543240e3667f26458e040868b1c6d39e6b94165ef54be9e94d8363e1f568ccda2470a71782ef5e0a8339b09ddbb11f3638d6abee0a141f9f3b9037aa9c091d92786e03aaffbd06c942e2b821fbd66c5633a4fe12dca109dd0cb41493623a306524201669499987b4f60fc9048be134303c72c50d9232a34d74ac405465d80c284da6edc0a28e2f1d9f411409d3f6c59c737b95129f4919d0d162da9cebf4815b43d0cb00809b43b7f8840c386f49da831741541a96bc88aac92064651e0657b6f522ea69c05e4d79018789fb41556515eec0652ed08936a51ced6b3d41e009e85f48b4c52d91467e9134845c95e8cad5c21e58dd35c106a573fb8a66e5bb8cf7a87642848bdbc9c6eb5ac03016ce456946dc4d478fe41730ba63608d00237b9a19cb0b938c9d368d80fd76b1dbe26e2dadb9e03e3c9ccfc7b447f16ed7d128ff3eecdc61c6805046176f193e86a5d673ab32f8f0df63ec4869d20400d42f10776eb7d23200578cb9fed14749c0a369c8cad33bbbcca68e88b17085efd69e2e551c5770aa3ca79c64a407028d11c929dcfe92a381c9b3da5125049b345e28c639858e594eba9a9068c73c1d592b3ecb1b5be8ca798966000363afc1832a9f1fc8ae7493ef7ce2093b6c488195f5840d6db72b75836764f721e4f05658b2c37d287b03458e4f819fe453468a690eb089c084039dce0a283d8e7f87cbc9b9764095f567023371f2bb4aaf931bc1e8fb40f3cd7da33756baba8a0b32840c212e7c8622eb2e1632db7f1c10c8f4f5831b91cf21ff5e24555cd764132c41ddb917e663a965a150bd9f6db24ee50bd3dfe19b77551462a49b1dbfcc745c79a96aa164a15e566d09bc640644ca5f5b666c069898c752f963b3d0d2ab3cd7ea15529bdae91a99497bcaf51f0367f9a1ad0328617540626ad1b25abd05c6bf8e7fd9ef907f6d7081d5c03cf4ce01fa54714a5c300acae21cb5fab6d470842210446877f33379a5ea36959aeeb543b0ec22af8907b4b84dada6bbcde19c780e8fc7f978b9fef1fafe3d8ccb455196124bda32c4384876a91b801c27663faebdd18559ccd077146788b426bbb79449cac00485049504ba3fc7c03f61bf96674e1c2c2d5b2c0cc1ed9fd553a03c5e2ffaeac7e50857bc599cee2277fd700ef7829a4f4dfda282fc7594527fcc7f1db918c19b376e3abffdd91ca10d0cbc79f306d835ff09d0ba7642a7891d9d3673b10f5d04279db82197df9edbeac4c1606305c22ed6c65ac638cd892b1af9af23c7268db102b98b9d3fa73d0ee4353a917e44c0f41186e02295397111f4e7996356e789dc4315df8fc11aa205ac2f5851583f2c1c9c60f5b21358297c12ac20f90d168f5f73d5e673b84a7325068bb9ba7295c31bb95a72d57455420e73a5a388d51265acd8580961d58395965762a5835513ddc4aa1784d5112b206dac72790355486022aaaab012555c5e4c1515cca66a498ea18aa77954a110a44a47f504951a2a292a2a49a872f82e5430642daa22180c15092fe50c1251ce98693af3c3992c2150213342504a851e553a42d8c1e6278449131b844842871825d29ca0a4052d1f1227c4d0f04493134cc6683122cc9db172a649cffd6639e31353729cf912396ee312e34c1c1c6ee2701377d25596dc5592247f912fdc4951bcdfe22871c6c27805b30313b43de4e99013f36f7f3831feacc3accff145527113e74c75ca71a7f8acc3ed7a99214e8e8a90f368f453ec0314b4be905ef12c4abde2f44a3e80cbe48b47c25d4ecc31d227e15ab9be128c9b36232b63b4339994a81e2e1b07a07c6068686868e88156c64246f862188662188645341a8d462b92b15881d33ce71c2e4990f38cd2ec8004e6046d9f518e52685c3dc10435946a28856186916164181946869195f2032d6610e47c63dc1837c68d71635c08eeac95edd3466981051760ae2f80758199d912cc983163c60c0988861e48693f4179a306054214940510c0004e1b268d6ba30218d5892d2833b880004357e8c4074861541a63a06fe81bfa86bea16f680cf4d0032d46fe1a94380161187270efd53ceb129779288ae22af630ebef79bd7a4d40a95dec08790eca0e4c1a0a28ef0270119094139b39e71ee61c66b283721cb98befa591d5293d2db79a8fab2bebd2a591930c28f7ea1a7907e3c85d9819e85c8ec81cf9d0c89e79a63df0e0010f2021a0b58640be108041460341ac138275fa07ec4b9816f93034720497b3688c52eab085381bdb52e79cf38bf768e9e972efbd65ce39678d53f29496c3b5847dd1fc08ada3c2605cce1d24b0aa962be1529a8e8a20145c05ac8a5d4b3d2474e2b01c56354669c728fa4a8d518480c6282912a523f8ee24fa220e8387171f60f47582df871442314c081c0214c77297fb6b2e68341870bb42175caf1749963794265054e18823a6be8c19336607060c183060c0e4f43c5bce7da424a4bab447ca231d21654a77a422a43ad21cfa06ed00fa04ea0675026d026d83065126d0255025d0245024d023500ea05ba8116811281b740daa064d836aa159281a140bbd7ae289d235860913e697b30ed3f36c391f01ad42a9d033e8144a046a061d022d830a819241c7a052a8183408340c0a060502fd82fe8002512fa80f6817940bda03ba01740b1a856a41b3a03ca058d02ba815b40afa43a97cf952bab01ec5a43d848ca4e7d972ee44872952a24081cae1090e4e9adc6083af061a982c99410625bda724680ca8130a03fa02ea02ca4391a02da02ca02ba02aa029a047f08bbca8bdec599ee7c99e2dd4092407dfd062d6e2027b851f9f6c53397fb1020a54fde313b497f8662ff28a62868c3486d3d63b817e80f04fcfb6bf8663ac50da3b6c424a633893cabce6ec3e89fd253fcbf564384311ff0fb3ff3b77562c8a5cebd70feae987cbf82b0525fca9ed67fa24ddcfd495e9779f343a69cde2c2232fd7cfcac2a4c16324147510cbc4043622d168619358ab9934425e336b689ff57bba6fff81fc4ce6bb22865a75b5bb4b8489a82bce6bd961ebd30f4ee239de12e24ae50ac4e24599fa3861f8ac4c3abed4c362f6734f419cc748906b5cf23695edc1da930725c5286398349e548c657c9957ddf7faa4aa780a5b9dcf04001c4199fa88a07ca6ad431979cdbf3ce302942a0324b21ff52d0c672f53a71373b3c6ab8798b3b072faf0ae06ca00c0920c094a32af629a673e9fc4d23043f6734948416bb8c324054368788d7a8276567c89f2dac249cde79365c9dab4b95627e380ec081912b224644ac8969031210bcad8c88a901921db9269c9d2c8d4c8d6c8b2645bb227646ffa73591bd9962c28cb9a9204d5eea0887649433f5905655e67dd2989c742b15b58202bc55661adb057582c2c0f6c16560b1bc5eac0eec052617f2c14360a2b859dc242b13fb60beba53f67b7b03f368a855a82d463a77ab22924d98eb5a02482b4fb76a7ead4232a4feda942d413d4212a111505b5886a446d524b504d5083a83b9504d587fa4305a2ead49dca828aa43f578fa83bb549d5adf0a249f535b1be17eb94aaeb2f0b5a5f2acffead7ed6cffaadf593f85fc64c2f706e8b6bbee6ba85d926956db65e9e7dcd06f5eddf9a4d8a6af37575fd203deeecb94dcaadfd8e6b83fa20f796f2e76f4209407ef82041d7c3d67b1ef7668ec0b443041df7b6c361bc67eb7522453e003e3cdc254aa238680e8a023d511d34455114d5eabfd51f0df6e73d8d9e41a9d02ad40abd42b15034fd799ad59f677408d40c3aa2b0151d8392418540cb5ca2e7b8a7e8a98322a71c3939729c1145d9a046f4e7a8d67df9aca88abe5814632047589753734740befe214a000cf6fa04c4b54e549ebd0ed447e9fc8c549f1c4620e8ec1b655ed90e5b9f82d49ea002c0d6a71fdeb39abd6c3699bc3c7b7e644ee0ea4f41ecfff2ccc6c4c4c579f0252622c671f481e9d3f1e1cca6fedc00655ef90a332ac3d5d569668da0ae69b4a222171a6c50e6d5a7c3d627d5a7474d008d56a40092462b2a2a32693c1394d7bd0b9d2d0c68006c7d02e23d188881d6d772df0b8ca77d87ebf54997a6e04139931c69cffcbddc9b8e0ff77676b93714dfc36fe0b7dc5b19756fa6967bcbf23cdc1b0fffe77fe5addcdb0e7ffe55ee0d766f1b8c0fc77d1001f0e1cf49752bd8472d7ca63f2fa9fa73b2aa3fc7b1eacf73aefa731468fa739dac1f8718cd8c445ca271aaff3a8e81ad23999d7514622c73897450a8fc54790e8ecacd9254d971ca6764331ad19f8f5a978f3e63d6287589c4ee5a335871e1af635e84c18f990b5f6b38e55beceb1f8e0e28f3eaf2a0ccebeee1aa1f10ee59bbcfcd93c958e8508b5c6b31f409772b56080909c572cfa4c289fb1c8def093971ff03fe0f4b148ed0f7bd0c6c0c20457952c06ad86a1ff4d87ee84fa0053444980032e1bb593db9345593aa497da46a52355a67aa75a63e4ed79354eb4cd3a4a9d629e3595c0e9daf0eff5e2e8a5c461e4d1a9787857b7319a99e264abacf0583e4ab7472722a9d9c9c9c78bc2099957574a43f3f6acacabb0c15249d78b5325892bcdccbb3f7417d3be517c5253765217908506a476e6dd95ce8cf6dbc2d1e8f942a23e5c4e3f178e4cf4dce65a660704fe1f57c506e82bc20d973e3e305836e829b97773cb2e773c30b067bc8cb44f28e9777afa053cf47b2cf2b5d4ebcd00d1924cb201974ea2de1e51d2f0f114337c1cf227fcd67059d48f679aed14dd08947feee89fcce89c773e2edf26c055e6f496ee2f1782fb28593249faf856ab7b4c056aaadd25a69afb4585a1eda2cad9636aad5a1dda1a5d2fe5a286d94564a3ba5856a7f6d97d64b7fde6e697f6d540b350509aa9d82229a429a3921f178315dec488c17ebc584c44e880d891189a1102b1233126b8a951033211624b68b9110f311fb110312d3c57631166248faf3d891d82ed614d32d79d18bf97aadef856c2aa67b110c3a79e29352b5d42d35a89e51a9d42ad54abd52b154346a963a846a4625429d52c7a8645421d432aa943aa5b2518de8cf552d754acd52a58e8272b1dc91940564ee655f7692a1f2af5c3396bc3bca505f13f194afb97ef716669b8f2d98675f53d27dfb35a51f99a794abe1ce947edcdac37095745fabe1de521aee0dc6e4de747e8697e195dc9b89f314ccb60f26b9b79c189c6078c1857b7bf1ee8d0c069d3cf1a5bd144a30fda551419cd486fedc4c6fe8cfcfb4497f8e2275d29fefa438f4e73ae993fe1c96e69042a54c521a2e510da9924b24433a43ba4407b68302c73c535faa434aa53f4fa194a92f852ad9b46704041aa5bd0b0840ee34ede872d3ce8e46dbe1a5bc1d1d06e40d3298b56594779988114e6e2797550092565454e422186a172e888208d080dc4017b908725054e422f84e3d9e4e9760070c20b3918c847c6524b9857bd3b9ed6415720af9488e917b2b726f2950b8379e3c249f9085dc1b999d7292fddb517d43c9d936f4e738fb86fedcdc4dfaf3733be9cf516c1cfaf39dfda43fd7d939f4e7b00db5996c1a760ddbb7956c19f60c7b096c4707c59983636edfd66153e9cf3794eddb50bbe702082c5d98b9dd6b80033cf059ef92cf2bd1686dcb799a02d3a71397779708986d792be3e7a5bbfe411dea6090e7c40be61def6bb8c7b9e96b3818b331809e14e54951638f62a8b4036109924c7de4d206e33cf8520fbf9c821d946b1faef2a09ff1258aaed0ace1ea38d4fac545b386eee438ee143c2f927d2671eff52928af453d2bf9b33231c15c7e701451138e3a6a32dad29f1b45e9a61563f9f6f14fc783a63d6dfff9acfe5c0d62d1f7fac4396c7d6202e22ab8e6ec33fd0fe67789731eb42515bc87c500886f5e7b65c002c170778df00b5c8842c75c85918e794f28a407f530545e57e8f5b17cfb40a9a9355b413751197f8eb4ff954b94b38ee712912a686c9e57155d27cd3a8c2df7fae4c3f5fa44faac4c4c22f0ed87ebcf04af8e39e6ae8e1d70c4bb5336de357a0da5638eabfdd09fd780f7a6b93ee2f5c88066b3d4d499404cdd0236906a818758172b68464647b8cf1d9579d5f9599fe773d273328392f98e51e773cc6bda75fcbaae5f850827e658cf3f6bed622fdf34c2ae9f7f5b84de1d27f7efe29b3fd72fac47bef7c8f7aaf138ae6188b5e6a3d6235e431de2fcff1f6a1dea8c3f06320c726a303dc7778efba9f1de69c769cee572b9a6a5cff9e6340d798f428e7be72eb5f9e6ad3a2ec7a72feee2999317a7812e6eee12797fb19bbbd690933bef7418f2ddc57073321311f7de221fc771df0d04b4cb13bf50d73aa4b0e1c4e95e3e5e75681bcbadc75d8a672f9fef6fdf25aea5be36bcf47be79c4ee6f5a76d35d714d01d875227c5fc21bea2c8bf8b9aec28bc98457ef70e459c4bcc66360c1286617886e3edb8d2c64ed73c6badd4715ca9fe4c8ca3388a9a8ba2c945760ce2848de338b2228e18b7e43851144dd3344d3eae5f2b4d93c595e5ed6317c53bbb5c14f91ef7288aec8f00705a6471c4e831ba66452aadc73164b366738e7255fa16c7477eef38eeb5e4323ef37bf7b5bdb88bc5897cb3e5298eec796b622ec31556f6dbaf4801dd435c0ccd86d0c31858fc9f0a39ff73ce55a4ebe9c41ab7a2df8fea39be3f2b74892ee731e1c4fcc220ee9cdf07db2c811d0f65104c6c46e85bfc4b248b7b484e2cf21076e8db4a1f71493e067b0337700337700337d0c56d882150e99b8f59029b8b2f76e4aeadb9c8431ad2d0257afe63e8e7fafd03f941f99ae70f7429f441b695fc7210b2cd24b16e5705b7022897b4fe423d67c55931072627590d9824e7b197f0357af172c54b6f92fc974492bdd7e8c57d5a309db74adfbc4adf2150e9a6006e95be45fc9b82aca444a3b52de7692acad8f866516c87be15e201b2db3d72b2288a2c8eabb8cf661697f6300cf58c6f4319fa97a2eac4ed145f4e0ca4917262d784d4ec6471f7de6bc1ce11f634cd99ef6b137966b5c67a49955c9bf0d725888cd4c411e24fcb17120989a614038974f59b25cd0b33e05c0ac48564503063c7c20858cc40fd4892438c169a233f2d4ba23491c5e525d65f497ee2fab9d972d5c6324986048f70b1f294048e178cc86205b442b34bc1872678778cee3877a5ae15ee465d609a1f24928c3445b4dc24c124273cb5366d648e01dcd65deb1c620c84965513bde04b0b85561214e10e52052c8aa28d9c6f318dd55a2b1c410157579638b204103e2ee10821f418a205041719aa7a3c41624cba42f33788fbed3896484a31dafcdefd66d11aa2444e9af2155240c2852754b8ecb8a04b971d5c50b0e261cc98716a64fa6d6112af76d862640b1912b45cd100abc969081b8060c2cb96ef016787966e24c3e5a9c70390236b8670b3d57483d48f148e88c1a1d7ff8ac94b847c451c93a4ec318965af72d0557663c6a40a92b4748c3cfd66c952933595025d3ab2b0e054e3108b3867eff6e5ba59b2c07c386a2ef24b94595c0ef19fb95c4ec7d2632559ba36566d803c5c82535b474a602be0d48903eacff18cafe170c07ae61b60aa2d2fe5b9db70e0eff7fd565f9db89d392f81b25e537e0d57d47fe4d885357fbdee8fa36b10ba768958dc238eb28b6c05f405c2c81086d7c33741a7032754d5100d29877e02951591a6a8440b191462e9684880008317000018080807849124064220c975e50314800e608446645e36944a83811c87611802310cc34088210018630c31c630c65859018621d1758f9e40655cf568492e53268830b4c85596ea483e2a7c0529ed05b523f8072b1b45bc3775096368c12a525c21c92916148396214ee46d21bbd7b26d45ce439ca429e4ee8ca10b31c9e4f34c840de024274c649540856fe74ad5544f8c9a039b36e6dba314c1f519bbac4ca66d3e49fb7f9e12353e6811f76a705c4aceca6371278ca7124a7cb7acd7e0468bc6657b679d0f1bc2ddcb474043f42d199f5acd8f80aa6f17008bb7c01e5acce891709088e6e0371dd577939fff3d050ffa94b78458d90404d2ae17f6f23966b21a80c59f520cf49de1816fa786162d4b271d3c9f8f72c16544bd85dfd8555b0ba27f0605951ce69d87db4a1cfb2d668a71eb3a1e82c0ab6f42cb3116c42b09cb206e381a93db44427ead706c94a65fccead8303a15d687e29921b14d5615a4e065da7853da9fee1432499d1041888b30cb7d96428b9323aff9287b5ec874903094b77af846132d44b7c1d7e413a4b1307d811824960a0dcd51b46a4ad5cf0ade66e66f863af956ff782c99568a4ba3126cfcb9d7fa70f8137277153710dd938fa4aadd19ab019495484afb6556dfdbb0cef275bd43d1f63052921dacf1f364682feff2aef6f5dad1f4d171ee5f63e30eecc856469207a5bbb6e28ea41d8831d3e98824ff5fa23afff93fdb77e030831740c3d9c3b1fdd1ae83b2c0a2f97716fe4c2004b02d0dda2868543a221725ca6bf7047c52693bc2d95413e12f406857933899e28bbf12e74791c9fc4d0599b8fb08dc6d483c1fc400db2cf2949e8afe65f7b52b48023c802c03d3694eff2642edb5dc130f8a40f73b8288f6a9061c5439cf19e573c2858d02d1b7477a2457cdacbbee7979446362ea43cb735ad4dce848afcb80ed8140da842cf3823b68cccb057ecb4757e1814098b705622f71103af66fc89005913a2927cc6e7f5086e6a9807421f9515d340b2616daf4acaa8b736985a1e34b71b041b75682615dc72e8e5e92755ecb1b1488bdf095993313e2bbbad6edf5dc26b8bebe3812eb298e89749b8530fcef01a80b4dc4468e41562fa8e28d0d07e2ba796c04c8afc3e92471e36f40eefe70ad64d1f98385a172f37f657f0e7d2b564db8806a752e3ac521c5dd846866e95cae69f02f40984a621c66c60f4963e2e1a3beccc5d5860364eed26004ad9133acce70b5267aeb627f03a659ca448f0f78d4fda94662fdff7f12fc14059f280a192182fe20ffce1d9e13bcd25f99bfb50da725a124b59f19457438c6dc8b1cc5237b16f2001f358416d007f6bcf10e09558cab7ffcf6e45a520301c0d5745adc136d0e350d7a11581f89c5e3f00a35469582427234355220e6c866c8f98afe1da885ff891e84656f0027a07d625a0ac3eb099486c5de7b1ad5578106b919f55dec7b4127c72ac6d706b7b40ad05df6a3f837eab71020227b741712ac09345bba6e8da24f4b449cc516f0830edea7945bf9f02764ed5263cc76e228c2be10fd7ee72fdcdde09db01d51adcccfdc1fcedd33dd0795ba97189afac6a51ebf0eb92437c7aa54858a26e228fde80321a8f33019f20b1904e72de5ed3456568d41bff7b990fe9e96a088be1e6e40dda589b2953247ca5ff96fa2c0540e23b0d5d9dab55e0bc09af6d04dfb87dbbe6ac1b0d4a2e6928bee1caf68ae78982d4c58ad14bca39bc6182098a1f6f28d2cf4a94e7bb2e77c5bf34e5811c69ea504549b7854cfc4c1d55294c51d24269f2597e1b9cfaaedc2c3d092dfa663fac713f68866b88c58f60e8207cd49aea0a94073b34dba34a0716868592475a8d466c6f10c2c588bbb43c748af556b02f95058adf2f4c4bbe68e84a36ee004ba916119c2d13e2c780b46343d437fc17d92cc7db2b60997bbedbfffee9241ba117379421fa0bda600965091ac5c17d623a455ab8d899c45df92e5ed676001666af20313c2db8298f076a41e4e170b423968ebfccc8b19f11bd9b241dd1ca6f4f4af2d371b700bbfe4fe110ff8ce1cf8e78d3d449f354564f33e1f36a327eac4155a1bc631019be06e0409927d20a24ac7110413fbf76a38c3dbabc3daccde6f36e9fb34cb9c1571e2ab70fcf87c12208ca6a43ad7744027f756005dbc248fabc0bfa3d491c9ca52120644db68b8ae33fc92b08804c1d9a856464548bed2d4471c1f24d8ce98c2fca0e71943ea61902ca80bf3b042717ccb29cb706559d05ba36a2b6230cc568e325e431c30b4d9d86b3c809a149f7320a8a9a5d19281b6b1266f8e0c81398a5fa95066cd3bdc68445b2cf8440ace5e4f6327d12bdfebd00bc403a4d840c239d4fd7cd852ca313871251a097e6cbd8aa1cbc3354cecd01b53a8d72842e754cc97a4260c408615d18e1835a95038c69674231fa20f25479cad5156a9244d9c8f10d1d6e72c9cf3cac3314637fb243035248a1f7525a171a2ae146c4488dd1aaa194bbc02e8e63e42d2fc29a280aa0f628fbcc81de63a40bfebc8ae2cd94e8f77075ab8f3e3023c929d1de5a1896a205cdb06dd8646c8e4b95f3d3f2a47c19fac1d8cb54b4b3b54c3eea3f8e03ef2b4cf7cc9e92afdbf0de9e90620499efeecf632bd3d68da5396cdd87c7c842a4f53eef63c071db55df5b3cab9587e8b3c1ecd9828d71566b86188c458fe1288365799360f49da1e2480cb09e0388af03a006a2ccb4e1a69891041b5b53199858e8d412baa6ab4310b6004ecb88b7bda8c41c3984f647227e5afcae463805af362e41ee38cee0e5d527cf5514502656f0ae112ada505c1834894e516e35ac046bd49060d86e6597f24a87efb1d4e3e0d31e9462cbccb62613cda374c274529f284b6960d81173064b9c63159aaeaa4d26f861742d3191f6da6e8b0a9cd48c59194b34c41379590b4a60b9822310b76e607becc43b8e63b72a510805952acd495cbb32c0dfb0c64f6a39d7432a55c6d1f87979080c3819a19b4eafeac58ac0c05959ee6fdb927c959bdd34dc7ae24973d0f792208fe3691037f4aa96be244de4e227c7646a57eb24e592e838eddc5a351ee779cc5dc39230cf48d84d216cfd5c54a8c27583a45b328c875645b325f3054c018f50f21dac4412053e4271be85943ab62cd10fb5157b718743ca122986efcb6b29a4407af8acf5eb042b5e01aacfbca5bff0b50a02c9fc1906f29fa4b5f880ec0b2ad4e5126ee4a691fa4a81ff393a3759df6e35aa2e9218d2c99733332acdc37d36d08a499b793258b60345c365180681df9cbf4006930734cb9e2e304ebfdb1f1b1d63680584cfbdc0b1fbd2b46d2ad8813be33a813d2c5e699d78ca498aef524f632c43b93b4da854845cbb2de09fab895a8b91a340eb5cfa1c572a18e8efa54a87a877c4acabf2d993c4b621ac83c44388a7c42977ea17b2cc4aa4dc2182213ec8fea9ec801311e6d6391264f553923a2f9e4cf3d766c12da169f5f56377cfc7f80fc8bc2164304dad7d78c479504174f2b5d66ab919a89c99afa1486c33baafe5d7385eb959bc40d9c8fdc4f94c61a26ccbbaded593ec2839becd4bb76717a407f711ce946e75247a6be5e569b79fa05039b050a8b0cee5f7a8b71ac5260a63fc35f38382bb6767af66021020a935568573ad197afe9c5c9994fc42a17bd11efa1e34073c96a4f2e087b79c93c8884a508276d92777af5b39f2d01b6d2c496949a83e92c80b09554d3c4767f1a2901f0b7052608737a950c3fc0058e4262e9c57e8b5c12656cfb5ae7b784b89a25378dd8c300c240c8055f3041a7a1e3de6364a8982f569b1b50cdb582f3f4be47af2ec44a582b5983b8763d2ceb62f035eeb0adc4722d73ea07061911f023b12541dc8f2c4de5ed7732afbbfce0390e51f8b1de85cea6df0e21f8d1e0a5d3d37b52ef8658e6b6446b613508dd54d656b7a3230e83a58439650671cb8faacc0516bb862aa503e3c9aa347d2dd70ecc2a06651a43c56adbd724f0a1a9f8392cefebeb260625a15ed81247a97f100f966be7121565c1ee938c9ac4e19fdf4cabbd217014658daf77686bf98111eb046ad2159783eb323e5956d96fd3071ca522127e98edb6e52ece278bd6c9d3432300ee24f811c4324074ad91a6b244355355d14846b59cb24cc8552f2be1b4453c87488f2d6e72ca527bd09f8ef2ce72b6d504943b2c07473b2a55a69cb2aecdac7760344e5b074f15a35488e72ba72caa8e1da01b989d59613b4255f33868c834352fa2d170e61a5e085db96035a46770e83249a343963ac2c238b2c9f5a45374f3301075d8d5fc448a96ccc83d0df7ec9ea1e560cf3f2c0cf57b9d31a81228a631035ca71e99c8190b01d574ff47c00af9da5e0d9b370405dc0a96a91f0488ca106c882b388bc3353968aedcf999ab5d849cb1b395c5a4842d1c648a951b7dc1047c1e08169d98e0d857a1bbcc0cf7424b60b09ed4690908574d8301cd4c2db902aca1191421fd5d1f51a16fc5ecff5089cc5c2d6c81bc7c004d24a544e916ac4fa5abc9794c22920c619b0afb96d67ea10b588b71c97ad954499b9a2666d959700549843b0d6cb14bd4b7385584e10d0841234a8b82d2524401169a026e02f7c41cb027726c84fa3c28c83d23253aabc674c9eb0981199bba54c600493c065b9218d4bb7e59fe4a9e67c24ac6b080ebecdd74a92fa72774c5e982f4cf94080b38ec312d1d6846d872ddd2735e3edf29a9a2ec1420386c4cd906a10efb4624ffbd55d6fa7da09c56d6b8f1758a9c273bac3042cb8e4e2457592171fef827888241f60940add95d7570ffd7b24dedeccf08155c69fd72d97a4faf11471e9a33ef96dac06a9732895597cd49e10bbecfe446a0deb159ec1cb86c4c28cec0dfffcd68ae0242d514baa625883fd84231a966a3224d87d9affef31997f4cb5298ce0c42885755d5e27614c71c49495f0186c57ee83bc8cd7d9ebfade5c6ede905297dd9395236a3a1cfe18aec6669cc9543800e0c5215ec6ac83b7b55a8e4198314d596954a7c58c7dcbc06c0967a70d99e00bf1677a30ec831f6249850f7d297567055a0f3f1c75edd4f36a371ab1733421637220fd4b5a900446a99d0d4f8bd11e7acd505ba1049b61fc0cc561295dc31f22df935184939aea7e457b7b5b5669120e4d116f00dd3fd28e895cb9d7b32155fb1802a366e8c9197e2ba32a89cad5373757ff257313eb7bbcd623b17c3388129753b23d9fc1a29115a1bd9f05dd2001048059df43107829b38ec28913c55fce84788a61c31205ed31eaed281cccd0898ab6eddb8e86e9aeb71d8f861ef88f3b23e88fadf60ac1029fe90834ae5d8ae7f0c58fca4c614381b60f62df7a9827e98e892449efbb66a78ad72ed1d8653d267553c6d16d4e1796300e6542c50b3392217f36137ab73b86334707b7d0503987df54fea2b54dc433f638baf0c5db263997580fedcec5592b24579865df8ae4f348f9c4880999dea0330807ea110087d20afff9150ba5005987937e2a471f49b5544a7f06e369bd49da9d4e4fba0285e534f93b24b9d79dedc8655ae18bb27a51ed22574861cfe14ce7a601ccc862b0c937e2e28d905c675f5c072c12a0bf51b09c8f9b457d2809a5f1c75ea7ac252d3a676a7fb0344f7127a9a94ddd1a167e636ace60ad97953f6105d426666218ef08417c5c1343ce218f9ff8ace5e60bcab0df00b42010e51e75f04a4c0737f6d9b86b4e9639b3b0be261e20d75d06c0586c40c0635c97ac1d9b79bfb36cceda1a3063a3577891427578d772b34e26ca1219ba998173c594341d6ef6d7ac60c0bbaa2ce526d6a8a32ca0c3597a8d5298c13cec101329ed64770aeb35d82e855072ab5dcbe66813868ae906ca590cce0fc0c5d3b9152685d657066552d9645a290582af79c3bc7d1e6c8425d496ca5bf8f67ed5040f94b41a17f95b68cae12c0b2f4e7002334b9d333acaa32b7a174ae35ccc214c3732c4a618e5b06ac4ee22bc95a6158cff4864806ed004b006cae19167ff31efa39658357031d5263da8770347e1312f7346f97aac2e1774bb82021aae26d77e28c216af6364e840ce61fefa6631af550238d2c75b40b8f1ff752fc8f080ef35f1fa4ca30a039586dffc7b34b4c7016b736ec6509431a0c10c3b339f09bdb3f55e077b5624b3fe8d11a1cb64094845c70c6c7b9c392e1e713a43c3c587d29382f570564156f7de1428be965baecad2698ee1d9cebd98d4010376060f4024129c9dc826fe03982f38b14a3ad55357f21ac4f66bf67263f7f17c082b0bc661d0fc3c51dc5742f35eb788237c1d99e9e3f7a337bb8c02138fbd96c62e4d1dbe604c179fb06c69159a10568d711ca09b8656cdd641f700c08119194ee3536da0567dcf21309cef55108a647494c087d99d9e7a98125c186ffb21be268029356d714de3e5b70aeea1de6d9440abac8076a2bfe94edf8646a6f8632e67933fc8c4425a811ceed16c022de298f9cd1631794326eb15f069966a9a8b27e378848525ec513c32613f0b6407d08ceb65ad570efb9df2433f2e0897dcb6b7cd18f1ccba4e2659f37843c660140e0ec389aad7f46ef43dde1997878eefef75bb260378f57f85f8112d7519ff52b36f0ac3407eca7e3b6b1ee0589617f64a5c7e39e905162c8cb011e48dbc6b79631a7c49d21065de1114ff69c15e47077ecb9270d9660bb8f063205d4ddc70d9c661c8d0f90cdbaac2dc88295559dbcc711dc43ff4ba32705992becd6120ed988db96b62a1d1c972449f360b9cbdd84bced2d95e44d2d041e5f8c5332ea3536591db78285181ca0ab07b1999e2559c1c2661149dca9d0e36fbcb7df8836ff570699a935d92afe9fa00ea527b2bce6c28fc1c4ba967934d16c0f1fd92a1b72aa45c6073124b7d8b357d6b01b9fc22d3ac74486aa59f143a2836123843ac93bf3e7ba7ae56f6477dd2c1b2fb2766749acb6b069f6dbed43c0d254a2d8438d9f72c0f379fb0a94b392d8fe5da415d797ebbb04b39db3edcdfe5b50a1c747b06705d7f95b0089c11dc7428c581bfc252665b6c02e5ea5bec76003527329b008ae7a08b415f04be29d85032191131454d4a8e755ec9a14797bf6962419942033a5a305eaa8beac51dd41a596d1891324a39196305b17e172e7cb81ae98c3f28dfbb45953efe044bb9cb57759907b6703e9adb209abbe3c3cce1fa3e02ca6a9e6d7be88e3f7e077363a34bfd4a4bdedfd6e6ed21d4692dedad371606f7322635fa5d9fd0bd1cbaa07fca13d91d88a2e3c4f72f28ae10642db9ceda2c5aefe8618127eac5883f994bd5752886056485c4d19df41747a171874319e0c2dff04ff046f73ab45b5f4c08988985cd3b6ba3eaaa3343cdb7b7b640b2f47b564d4f9701d20d94d3c0ded50163d3bc4c940d5cd4bf8c63f35c43302433bf7b059b5c8d137611a90ff517771df7eedb12f57dbc4df028d6b73d300e9aecb05645c9b7395f05006a966cab9766c07effe74df4407185cf8f360d8392e31cfceb279f6b563d3459c7c2df617cfba99f1a944eb4e20aabccd5c91b7cab899381ea4d21cc549d3eb93183187b64c22a72d1a81825faf4cebafc2f00fad28782faa20208b3978dd145c098209741d3d29f6937ee638f7c08cbd7ff2db62cd6b7442de25fa88d41028a84fc356cef3724df099887520d58a6ad272715c0aeb914e98bf30219c94b3a5f41d417a2a54b60c0b50aba31a5717651fef67e483939cfa1058f96dcc05a6b505145940b70c48c934fc81806dcffc02fdfd1da77767f52304491003a77fe422e424ebcad3ace8421328ffac7eab454ff81ae6d2dc53f7db4fef06fe96b09815261292fa2dc15eaaa397d084e3bffe30e01202e3854508ec5f36d6e98802979a462e87848a1c8ae97670a10b00cc3eee2a7d4e7bad56cdeeecff5d9f14bcaebff84d70fa084de43731806fdb20675a581d09b0c2692d371f0ee75fa89f3fa8b1a6451b0dbab019e6bed88a8ca4a28173976d61c1e37e11073380449a94eb4a719328dc74b42e1c93c9fc1c0d7319f48d7cd41753fa04f56a68ca6467fea5108e0cb1dea11ade5d93cfe7de95dc5488f05ccfd213ce446a6832938d42c40a0ec94110c081c83532cc6e45524182df9f08f414fd38317c332e17033b9d6340c95ddf0dabf460256d69cc78c9f37102aa6326cb9091fb3d31654056b3268b72a937d5ec28c1c82ffaad440183aab19ac66929e58b9bd1076b82e00e0acff4a99229c1a6122220b8cf1d49cfab2ef257558f141afa8ef808047029834fff894355d97d1e701229b5d4ee35b325a70a38e1961cc9e922a5df17d45c21cb181b34b4aff7d147570c111407b8623ab4622fb05fdd15462471af126fa55d913c5e1b3f5736c07a6697d8f785d87085630c731059bacfcbdd8d678daf6473a881988795445a1c24994b662c755196ab35ea75c721b55f60ba01114c0497d78192ce2155bd614a8c6d4df4624b5f2e41d5c76430fef8b8cad2a2560c2d4aaac100b19964775639b195d98416a02385f3042a7e909f69700273baf37445ac2623cd99dc4df70cbc39b3a3cfc0219dab346fc39b119f3576e460f5b23109f791e407de240c80dde6d37b83515e4faf265faf3af099b987b55c116baf4a284497b2b60ccc1190f2637330cd631293f45e89131711ef6a045d412018f23f0252c3535f3db7a353808af4506c6899b3960a00448a97d6d7e4ee57879f99fb5acd1d64e74d79827409c7c041df86f35e7806a3f1a520e49fea326901f15c1dc0225865a1ff4600723ee957de1e1d571b783a2bf65bea8e3559719057bd4c8f26aba13af6b3b9b5d55c4156df4a28449792ed34fc199cf14173309b671a20f15f90d31711cfd504a082451efa370290e7935e656e511d0269761ec552429ea5749d1192174e83a7beb8d5a6fa92fb92e1e6c770824925ed396ed134821f72bd59970c476bd7dd23144328d92e904a85643ab683767385649a87d9ff6416ef3fa6b0c08690c40b956fdf45368a5fad4dc7e3071e3c139efe282274e0c7838a16da3240ce3c9d04d4f95cec758a9de301d2e1d89a1b2752cb751c5a82d1c2c8ebc9ecffc95921f336f17c0cfb50e9a4f048b903d22fd64193d599d3bbcd643e838d72cd6af0055d7db8946c22235e4298d8dc38ff8d4f811840ea06d10cd9789308546ab5554319ce21c3a6a3857c82dee1e5dd30b48c64d8fee9baf1e9914834e4bf38f871e88e0e699e1f9202fc67828267546a1e55badfcb33da2cda971b0c1fe053487f87daa15f0ea268241aae9f03540d109a5ee772aedf4893c15f8b2caca08c460d2e7994d724d11a631442a4d1da92ed995da6273515fcfec27d388e2a62a92a52f26ab5cd88af9bef1a87811619f671270e82a572a45ece4d8ad21052ec4a112521c4d052bf740ad64d7b335c47c5cde3753400cf42f44f2f1b7ac52452b6a57293b82cc21a0b90349684eb40d70936f9813503bca3e40c35526b74f493537340d4024b472c2029fc22ec7c537c7fa0557e66693b39408dac1eb972e2c9418e73d6821fb5d6ea8fae3a41dc23144bf21c2c0f4979043ff09b64d59288e06dda237c037f446af4f6df07169405ba06e530cf8af4a975a3701814f8da05fdfb9044a749e08cebdd7847fe02a043d8698c8e3bdd6b45e6f4be1c056215c9d6754100b82ebdc49ab3ced282e5290c2599b3b88e0fb1d82513f8a7cfd1302b3040983914d004db4eb9400dd19226843e040e5160d41f0b650c6d66ec6bfb21657d52db97a4a072ca2a39d4fe2f19d7e54591622e80a434337739ec8822e589ff63c432b16b66690628db9523b51ecaf79506a912e3bf10600e1e063a98131fefc4995d450f07732216b55521419ab66d634de5e642e835b0726019886b09bf45edc3bd09f3f511d3fb50e0ee3316c9d052ce99efbb94252b5ddaf4d1f2d2eb6322906f4be891f5e06bcb709df30c267e559ad72c58b98d38c7a9894de601d5dbf2dd57575b887eb92a3b129aa439def8e11fd66eb0e279c700c056c410d8753983bc0983ae9c35e76e5aee3fae04010ef0e744460f241d642a17e0786552bb266d689d94bd01d7c59cafed4d4d0bcfcb57f9f367c77173c258f540f473b4cccf298c7e4ef4678e4c886f2939442af3336254acc2902884cd5c748ae54d1a2fb450d472d06a00ff57af444373242a7162b694bf56a57d6a1e9cf20a3308d903f215fc09a4a10d7635428cae27521ab880e22475704e961bff9700170fcc1bfda3e63024014e2559df23f729cac9b481a7d16a1ecd04d8370df979337a8eaba8ec296522a7aff6837d9bf61ec0ed43a50203832286ca91c5e2eab1a2dcf5b354386502f07d2d580799305a02ac9ec58c91527b3c8f7eedbc98cf770f51add31ea34c4a823ab3cc6457d420ede21547d13f7d2860aa16122299741c9f36de441d138556d0dae83eef445319bc8928bb885356e27adfa2adb9eb38b0fdbc16f360db97fa33b1c51bff40971465deceea723c414ea07896d3ac42a2c68da5859d413d7f0ffd17a53f37593f3464dde618eaa1a489c6513a2a9727ce10d9fe87466d6c345f3f349022ca1aef0764efc6c65da668a47e99682f396aa6c144639c3a3c7ca967fbe792363723fd43f6f1ce8127c3b26cee2b040eb2d6364ab71e7bb1af4143c8bc454b66f2eeb0b3a142def02baac68869c3287e0f271128cced18ebc1f2b7c54aedefa086cd5ae0139e27827310fa3c99a28207f75b5ad3d323da3037d0651fa3ff5aa4b6a16ab50b8835fc64594308bf1a1688351a4e3c34ccd57f93d508ede0891ca307b1067385652e18b1e139b68ea13aaf0d10e458ac3178dfc4ace174c1632833e01daa6e833e3ec917651974ff5d1be81910a9e96a73570fc51c075b1c2eb8e7bf7cd2a2fb42d80c53ffb68d367b3e2af9254b6afb8e1e6a5930488cc8592979ffd63df8f7133612e420a8bab704d8e903583b80826e9317299fe9d3cfafda9a9c44e6251f6a7662c7eac2ba2bb0dfecd0722f7c0aecdb4b8df27f082ef5d28332e55e168943cf016847da6a26c647bace4f5727f71a21a65de3f395d2266c1ba2d109219b93d2ca5ee22df92047bc9b82ed3585dc1aceb8a45d12d9e31d9666dce0d3d3d4639507c92272ef5e7e6e2b242ae59c84c8e1e066c20e08a4ad9ffbdae2377075ea5c9fb8b9a642759e983abb1cb9246821ce6e785c12b82426f9c0c52cbdd815e394b98b5dbd74d1b2d898b4d6b5fae383b1abcea5ed2853d5d7c0d3e8a0d061a507851a96d42ce4129fd97e0835a1ea22655fc07b0d69f0d71323ecf316bb673915914422500f9ca730e9d7fc50d15e7ff5abad842911b6a481682dd8761e05f1fa5a48fe51a8788f0171149d1554d1e5afa4345282e3fe0a1d86aed24bf0628c82ab0abffe1998b08befceb69773d57b54f54b41e7fe7e664d9a6b680a0f164062dadb3bfd92ca3a5af62bd3e8d340439a878f473a342148ec1f1d3198edb0b76a7ea907eb3919f642205e18175ff20aa04990093accd5316903a6313c9490526600cbcd68066924a9b3770d9a1831f251181824966a0d4406af57aa2dcf4481770c197d1bd88654e81f6718b01c155a46846c74577f1e8c3ad197932052ce92732f6204231d78da6a4cfe7aba40092ec5b2ada6175e641b3f5cd76c38ff6f1fed8259f2668e372595453c98ea17b4fc53cb15818675dda5f1f87c0fc9c2433dc931704dda6ac106a5c531ae9d52a6d6b8b02be97bc188d96f871349fd9357758d5b1d7cf8ff95d2de20c7538b2b010d2b5d534a9b32d4fa5bc7495d5be2244e3931b71b0602357c81b747e238fb3ab5350407bde17c2c364760f8fc58516cd56566f90bfcde3b6b1bb93e7ee30593fed726412bcda51d47690df8af7ddca3919492dee5c6a34a25f8517efac9587c3bf7cec61ddf8cc18eaa168c954146f0e3a401020bc75c2a8f4a88b023e1e524149ea531278d297c3427b20f7ebec467d1711e282d117dab500338cc68431934160afc67440330d85a691d969107802020eea80c18b931eec858b20beeb09fdf78b11db45439d058d64d9a67fbf0efda553c631e7a80e0d42588efc01fa9d3367c14265f7419c49293f743272bd70734ff5e2039b49d7a38f5c73fe7a08c431915ed5418324debd5ff2ab2262747ca7ad1d5004b7b6e3223c68ee5d85cba7065e6815acd627599b9d17cf2351ea25dc49cbae87fe18b780a029ddb2fb686d26f1c01d70c0830dc4d493a45fc42cba4beb5a22e1f428870112477a7e163189c32fde5067068084470f612f24e573fe85dd451258f522572fd3fedd7e0fbc776572193cc5ad8cb948b0eb55711ddb3c4475a1f4d3c55e54a02df8a63eb9c76758fa23b29f63d1763cd565b781b5a700c3f029b8ca768f74a84fa8d8fd2e70c0d01e63fa7d5388a0d11ce6cd1aac6229a39aca29e74b354ee51ea5d0bca91fdb7ca12bd07c0605617808ab616148da97071ecbdcabaa8fe0498bfcb4c41244067e8feecd1a0dc2b427fbc97fd9af8da45a187a13a712b75772a730189f49313be5a68524154fcab2cb9b30bdea7f5cacf2e66f007d3f79524ce279153403aa49832fb27e195f543c26d44b2298f571880144aebf40f32921f0ce25921b978d8e8a77be6f477dd3d1738d0b2238298bf6d8bd7bb278d26a5fdb5f35056b803a1c842003ce48e690422cb5a60b42f59d6f42d60a0762cb34c809ee65e54be92494bd41880dc4788a13446cbda260008d1294724ca80b7044244e4a1951122a5e33243314446b35863b4acae469910aff0cce1fd80929695de18c0721c3396db537d83a92561c9f2d10205cce9b4ac4f68afee3b2fd6f5041fc64afa389914ba4cab31e56578df528c279e80f05ba586a9c2ac65820bac3e28d5046eb58398f5def42de3feb2cc54496e2766b3c5fd3a9cc513c5ac6cfc5f25a647d4acd59d05f5127e913f3cebd32032d7b294d2a8288338fca365cd4c53a0177e07c75cadaf664d62ea0c67b8d79ad559f640fc2fa0a217fca6cf1ace8124159e10f00b045813a5af03df624f642abd66a538e71585f585520790c05d62b6a958b02390dc6141a93ea25e0dc974f489996c56bfaef18cd105675a0a704ea15f026b2a63b0aba1cdba8cf86e15e92e534796b63e99a2a520c8daac89cdb1cdba52d312c125c756dd115e7f3f73d768ee08c4ee6b9bb5eee190d59f92106b6973b3e6b3c3ce3eeeab11dfca6ed6724ff77aa779f42b64450e629f6279492e3de70a5ad19ac53e01eb80079ee3dfa643e7788d2f9abbf6dd7317c0389c4febe87b6c62409e31547f0c46cdfde7974e4f60e3d5d3a6d9998d6ecf91b284bcf94ef0cd1f2d3c516c5a59b51cf9877d611064266c135d4e31065f6fc02d0bd5719672640e44ce927a0e17b614414acc952072b9a88203316f0742f02d0da433590b281fd24b23e6f3463811ffa2c579207aff5b8f28f569c70bd24e2ab33e9d8f60e790597d4af7e68d5e8a5e0a5b49e8a9a970dfaecbf12160d50826a8026c021d041d322f2a4487540657c66ec6561fc45e604c8da6dbd410d4662a2af184d2dd56cdb31033a669301d9d5b5bfc4c03143bf3889ccae6df9ede544f110192efbc415fbbfc86cf9766a63e1f3a3b3bd4e36edc64dfad7eeaac44fce8d4f6278c8fa983aef9111865eaf7c7187b9b06f52db3e606ad7f9549276f9e1844aad68a8029932bd7f8b8ca057de29f28df8a99e6b27933d775580f23b6cdfba57366b2103069c56ed6c1daf301ce1eb7d962ff0f696e75acc61cf94b0ae6a77233c9d69b1a2b698284a5d384afc0a99b2bdfc0b84fe349e6824dd0c9c4da058839a589499bc97282ab409df0eef02d2e9531c1e74b6bb6a3a1140aace0eaf188b5ef690e8a6fc1c6aa84f25ee811121b82429ba02c61ceb68e3ffcf861d465ce248c69203d4d82eaff80d03f0346f4cd3f9446faa20c4f33831ff16ac72a93c641a419c64decb30810b0dc3f3080d2f90154b9b5bff663b4bb4c6bfa888f1abb6afe932f3496c5bb8e36f51e6ceace773b0329318cfbab3827fe86392616593d556b7f2bc4574d3c33e2a7adc1993a1aa33b379b16cc273292a21ae55acfa3835484edc159adb1ae247d3dba5b74b7246f88680854e202aaf755655c30165aa5035ad50d5ac0a4af1c465dfea8daffc38591dc620d6cf8e76aecd8cfc058717789abdfdc5a9554800cd7b6d79acf7bdcaf007026fda845ecf313b068c73698a1f2bdcc1628ef05609aa17ea39584ecbdf796524a295392013606250676063b6af5c94016b82d5bcb63ad0bd6eab0b6c7da1dd6fa58fbd3c24e3679b8c9b950f3db6899a3ebc99a84ae0750eeefce4be9e038182ae5aa0b1cf6d70f6353f62f434ed7636f8e1dfeef60d8a270e243de7c7e5ee001438f0320a0eba17ddfc3e1ede7051e9283a107e50e8080ae4702b2cfe40e0e9437468eb0df713539da649c7ce312c5d1c8740270bf2a02c7e5c6aa2254df794584a87e5379a064757fca6ba5b8a442de521e902053e010a7250d1538b49937b2f540d9f9805d0ab97a40627096dbd84adf33fdc206488f9537cfc77fb8e937407aab3140099ff7248c00009b01d4d4a43297400db6b8a96cd7a37fc7393a2f9ea103ae6c2977b52d5f4ce5cbfb55fa653711c614ec9156b17537f1bcba99c3e6d25cbacdc9c1695d463c7735972df0cb6f5746a9fba6edc8cc9b7f3dfbd117d772084559f63522e3f537ca51a15dae7b2918c1edcaa10f659ebf5cc8886b81c3ee12769798236669c459f2a550bbac607f5924841b24652fc7256e00cb9baadfc6410934818043592d0cf3b7f799bf7dd141a0dc9b17f6f710cff75ba171d8df91967eed5658261b6589036d64b7d36b6bb1df4cae7b9f5eb79046e96fd3d299c426937da10c45a83e63e223fbb00d7088e77c0993659efc8c49cefe2af19be9b72833a2733a6759c644f5f331ccca6ff04f4f87b326f73b9235e7fc7bed776f3ff5f581729665d95592a953a6cd690e4b796edc73df8502873753ff15f794fb68f7b791e7b282c25632c77d0cdc73dc03994b70dc5caa4e1fc3709de6db955fb85a751ff785e0fd5ae5772f3fc579e1ad216b7fbdf046bedc77de7490d3761c286bde1099b36f2ed138ea5f6f475afa59a0866ef1fa38e3c5bcad6810c1ce340fcaa1733a77fbabde64efb973773a1c6e2e70ff0c71b2bc1c8cd354dae65ca1733ad3190b5799732a734f7ff63efdd90794332f9c977a40790ef16cbf151a87fc1d596d9c0b4a238d300ec6b97d8cb46e91c619638c51feb66d5bf470b49777d32ad5b22fae629441e78b40b9729101124600808d1a32312b1a3364c0c480f18255a98edbb4ecda4aa7f45e2202b72db2b2b2b29272b22aa2bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb7b85a4bbbba3d8a2cf39639c33cebb74efbd59467d3ad76fc641cf3732fdfec221326a73c96a772edd4be712b4563a95e61274c9c1a65fb3c9d530a59558638d754a2bb34a2d6a9555a416555a91562a9d5f7d4eadb51facac601928910e910e914e121d984e121d221d22239452a79261b57eb413e660b495ce1e289d43f3357ba074c226cc2910b02d52905479014a08ca9523942d4f5baae408654bd39627b624dd0e70b199456c0bd2e450d01b0b6825a233aa148854125b6177cb6b0b087086b4c5f564ab6869c2820a26b4c4b4c040cb0bb434c15a946881e5e90313ac25e8254b148c293184402507597c806fd0a54123cb144c650559a25011ca9294e5284b5116a26a65c6b2e8b0eab68c804998598266f03f453d3df59395f954057b73067faba7a6a7d813d2bc22c8075e3c1df1a4e409f61484250acb1316272c566059ba589a6031eaeb8214604e6019c2585e36b0b8ae68b962a5872956ca5cd290564645e095d7952015666cc1d11f95fb866dc5032e9a3cfbf37896d91ff4580a82b23fcc07b213414d8172ca2107d5942314a8584eca110a1413285027c85a8e50a08872dfc814d84023e40d998e17de30e5943df017657380fba3b4f4d26b69a6d55ab36ddbb65a336da3d756207091f33807a56c5a4691c03d54af2c5d295644150d11f59010d71d52830d317b7b33aea3cf554a29b76d1ba5cf755b66af8f21cef257a5accccdab81a32fcc5d4f08161cf6d0100c6608e3fb3e2efd1722574fd97663c8f47eccbe1a1cf421426c3cad54ad0979ec8ee2a07f2ff56ba9876044d1d2ad7bfbbb7da1e7ede36398ed7ed4386f86ce5a0fbff9186b72ea43b08b35d226d37ebc456b7a08047358f3859fc31a99bf8f0bee36ac5f5e60981638eca55ef256db58752b81a0939393d3cb56f14a75a6848ba02f86bc80eabf760a79adbc564a79efbdf727cebdf77ea17dfb5d25955352a27689a383e3dff77e3a9ce5bf34add31860ce6722484120597e04298d61e619e913d9ac7d70e646761c07fda3df449de8fe336e63044309430466b29c9e00ba812c9f7a4dcdce0e094e78225959f2e2c88a0c29529c46d802c6e5888bd0e7057fef74ff98a5acb2d69fee3b4ea9941fd7f1c3a9418931c8c148698c34d2ee26b99f1ee5fec221d25ff54573a0542828074afdf54a41094aa0c4d631c621293189bbfcf324940bafe2593815cfc2ab78150f224eccc5e8d1a3475963a3e3d97f9331d22c94fd0b65885ce477c3c1f620aa112967a553ca1f78c07d23f21063bcfe4d07dd05b19bfa0a357105b781e21584a61811e0e9e08e2dba923fd597afd68709b96c6ff6aea274d3b8d760bc461f675ff8956e4f3b1ffaa9dfdc57366e84e171f677e48b17f30dbb49e84bb9934257ca5b4d6d489d73e44e3a27e6cdfd29a7aa0870e74ad79dfcf61be5b8bf5f2803e74e6678323ceb4e1a0775ef9fdeeea48fdc89d26b5030cda77c6951bed686f66354d1522a18f345fb2aed7c66957965eba99bf7d3e3a38399bcf75eae4ba9f00b8cae078c97a982cafd37e585f32953dbd1f9d4b42877f585b92f04baf34a4dde5879f3f22663939165166f644496fcede3bc31478693af6c09d8f2f6cd2b8d83ae6eddb82e2539d5e4f00b8cae478c2f5ee936b29c57669579a5ce278e698b334188094971244551aec97189289e721204705c220aa7fc392ec14425bb2655cf7961e790fb97fa1dd439f6eb10bff7e7f729fb859dddcea7d6bf3d75f5e2d9c0f537dbf55459b39cf2c29909658a83e8cba4bcf5788c0e58fb42a01be30b1997b3b761d32e015bcebe46927938b9dc481a47ccf4b3ae87fa5b22f1630c9f98e3dd7c464a07e5e570ad02cb7881e7ac745e6165bec03dd064b8e0f9f2278daf7dda7bf7c28d59d3b2ec7e1537163573c659f3a30ba60838bc795e0efb0d70b42fd841ae736ee42eee2bf6ae6cd713636e19654cc1087ebfd7d64f52f1d4060cbef31d802394a426775e6e7edbcf3efa82fd7b3ff6b59ffcd956ecb6ba5c2e827b99ac01403c9cfdb69583dbc338b8d5a71fc2e4ed756c2d9f78c35bd96fdbd7784bfbed6dbce5f33d369dbc6ddbdfedb96d66fb2c10997c8b1d76e0c1872243433dc0b4df5659f892c3981c252bcee9bff5bba7faac9b1cd4bc49cce1af7d23792bfb9a4a11fc2f73e28f1963fcd3a3f3f1fc5ae9e37edc30eda5f6421cd4b4df714de3fe65071cd6646d9b53fbad230f98fbcd2badb4d24a3d4bfaf167f6af9d228df62f3ee0ecfdc31aa7bbbdf4686895174eeddba3a17353d6de7ae190ce6b260e6acf7961a492b5e7a8e0b09d74985e1fb59183dac7f800d6be9d348aa36ca7ac7d8db7344d7b1b4dc92b6b9aa665ed5b48fb09f8cde4d1e20cb216a1642d4691b52ab2f65183ca1a1728214dd3b4ee0befd566b4cf02feea74303404ebe1fe16e331ec222f0f43caefe5230ccf06209e9797efe5a5d7df8b37e7bf783586200ededfd18103c3096bffb203be5913eee517e22bd8fe86df7a29fc7556bdf5ba8faa6b8da8be6ed23ee05fad42a7d97efdc203e65ef5f6b1cd9e7a3474ca3372bf7a3474eebcb08df27dce0bb3cdb3c1c1fb1a478507f9fe7de9b5cbc1fb9917e308bedf47f7defb5388b7eadf08e594efbdf76dfc66f2dcf701c45bfe37265f2f5f56be1f439d7c97f2dd42e5be387141d8e21637d5b5dd376fdbea716c18066f1ee65248a9f00b8c183032522cfc0223068c8c1934300823068c8c1934563135f972383a516905c210617e1c44e1c2689a0c4d9ba16934346da569319a26a3693526101f0b583de8e365013035336ec349d8521e10793382112e95b2a919dc4effa7406fd588a03f107913e52acbdd8ad16915636c0d1b00188184f7bea689b1e38d00a84878ef2b61c6841354a9f7be12664c38a1eb8142aaf3be12664c380105ae763c58dd8abb1f2e61c6841350005929d44fe7572b1c7ac227ef8c1164a510801b342ae0d871b01f5bef66b04113729c29e1068d0a386a04b0c2833352fe4cf61bd8f564d6da40a1ebb152aeecc5297012df96f5689a5301478d0028b742e50660399bae0750be915b6e28c82aa7c0e105008d0a383899950d015a373805c891d3f5805981c60c95ca86ab99187304e8529d4fcdf5bb05c891a36300165ad8f907738d0dd7a572e4e81880851676b2dc705e06000393a36300165ad8c93c0558e101c890a16300165ad8c93c2ee4e855930ae770e05f0ebc91e7ccca6d260d1a1c0b2dec641e1774f4f45310671b9f57dec24ee6714147cf8eaec7cac15f11c1e6e06acdb5f2d4ea42ad3a6aeda93531d70f520179436b24ab3ef57024abbead61ed84f94dccabfe2c6282e42d9aebcf256fc5274251e18ce24a4439e4e851088e72b8aa4f42f0aaf5672cd7c994ebdb76a55a25925c83c821978372d85272adf5697857b27c3edd5cd82970822dce6c3149b85fdbbe225227e4d865996a2686096cbfbefc50f5f76be44deaefedfede7b19305ff5b16380ab3c234770db77deccb4abcd948f1d0733957cef6fd98720e837d993cbe522c273bf46c75b31df7cffc7c72936f932416df9462db33193791e9f84a017895e27d435c208941e459afb02cac4647dd0ea83561b0476ddbcb35a4e2510a7c6efca25cb5afc12438a686316b8061becbbfcfe7790d5f2c88b28fd317aa1063838384a70d846b8febd18af5646f407e91287e21835134e1ba586805db66c295527982a11c41c92625155441cf6e492ac36e24c41aa10390d09654be91b7925188131c55be0b08d421c8af32f3bc80d9cecba4532314a69adb5f6c82821a304be55b26c0993308f792c2aaa8dda48ca2841bda04260336c49e214248817386c1893924dbc11c7d594ae53d629535f28496962c147e6c442e7d03a651019659451468ff4c96d9db64e23d8a313ed25cf766030793d67628a526030180c06c284c061c372146f5179d365300db6c138d885759feca2e44e741152127620d3973904c23f6855119992e2ee430137c8fea1a6b4b3730587f71e799efbee3537d583b9236fb0adb70a0ee353ee1c7963ab92f4bc71ecd4ec4425f04e400ae1303e09e172cc25a3c81667c2de0109d92edb65bbadd4565a042c7fbaf5ead4a7ac74563a8980a5bbfbe4e24e7dba743a259d320abb539f2ebd167947c96ca5b3d2a93a02e7de28c447fd91ac14979863755b49ca6692dd94d94a6da55dc427d927e6b861a3a5aac011808dceb624e9bd88c3dd0ba45f9d233f07c41c8ce0e7febc70d8af259b2852963ad5109d232b9d2929553cd02955510ce91c99e1d4a4806da5b6522d3f6da5b3e6a5d259e9b402fbfbc79f582c16fbf97921fdc89b864d29fb255953e030c6ba2d6ae14b67f573315eafd77dbd24f8ba5f74c21e6315f020b7b93a3118a716807039f3d7561cf4dfee132c3fc6228d1638ecd7cbbfa3aceda248e30f448826c28a7ed97e7181c3d8d4312052058218933f13c7411d1c1aca1fa70687823764a073860ee496ad1a0158a55cad1cf4d496a758ec865cd954973b654fd93de029bf106bdf6f3dec986fdfebcb5239c061c7c27e65fb524a296bba630c8cd1693f20170a0e6fac632edb6d3677514f01ca742121e58911548c8b299804d19d40cb0f497881e58814120541b2891e38ecc0c411d712633164718287a418f06004125fd3951ce450650543d812d45de4e0209974122190a83cc1854814228824b19404145d24418310b80089105a1cc112693044144060b1031055b6a822b422821f3c7043135eecc061b0ea7205890dba48a2031530ac60892cb080558195e0c824c10e8e4ce9224b6e88d2c4143b5c9164c8135b3081b2820840182208490898ca91a82c4ea220b1c4674a174d42c4bc783909922fa8a248d441cc620827945080450aa11994a024892e7430428b2b46ddd442907321948194589185090e401082272bc0f1031f7cf18414441011c20d540039090289278cc0c189245370b0a1e3a7c791a8d1da766633055fc8c0265322f260ace1ce4999072b08820a23a81d68d0e50b27393a71840a9ab04192144de015fea249a64852583a25849843b23c75855cc5d4b8f7ba70189d98700f3ece17e2d8cfe7cc81aa82644d1264512014b45dae5fde544082f2a683e4e3184475030ebf47f7de654834950f584a2965cdc67991a7bd705593e38702c8311c404da4f10fd239f21340c4e1ef9f0ffce69da7bdeb93caaf031c46a7201cc62e3dcd4bf0c0d62f70c2044d8d1f36d00f07154bb878ea03f1d807fa41fffefd6ca00fc4133f4bc29ff200fe67a900a90926576c4941149e7e20fd3b3f3f842458b878a8177994763a67e6992319a8e4a62e26a010421731c1044433018595d6ba99300153b7bb8fe71d0793c0e16faa9744a2644a2080e8926790d2df6f5e5c3f3fa27277751d119f1c5122c7217a4a5fe1f477ee27723f092acafde44811f7430e6d7247c9a18e677f1fcf5e65972e59cee81cf9713ee0cffffd7ddb7bfad6054f3ae79c73ce39e79c73ce39e79c73ce39e79c73ae82204a42a59c934a2aa59c53ce29e794734a29e54c72c3d63f30fdeaf23dcbdc7dfaf4e97efdfa74bf7e7dba5fbf3eddaf5f9f73fafcee9def35e44c4a6f7372507e96dd2cbb52be0c4cd665445a654b9f34da68a38d36da58239ddd8f76ddce5aeb8e95da660b220abadb8acdc69ae5b86dae5c7f8b91c9844d462907a1794427132de2049ad588c49c266a1c95a80851e7b4527db9dd23aa44e40acbe4011d8830c5a84f15aa72a91ca8c3c15a942bcc4123264647b91239586b51ae443526094fa35c796062a4a4c9c11a66514c411aacc8c1aa19f1967f31fbbf28aca7a98d69ae69198b49cb9cc95bd4041ec5fa8ca22175e5fa3d7da957e307fdd5af3e1b70b703f6afb123cbb8bea234cab2a82cbb9f45f90d0a2ecda50969304d890e411a932c4a7be96094438d2857ad4833d28e72fdecdadbe4ad701a39792bacaf5cdd8d5092432e5f2a2185d130548301333840677ce4a15e0d06d0e080128d8f3c9910a64792551fc9a514946bfd507be5fa3220c0e1846d3a7e36e9a29f7449a01a3f56abcfa324ab4a56a59e84d558b75c72b0324929b2c95b313739f8e2ee6572c9a378cb934b32e66094226ff9d7bc6d30130623a3dfc05491e1de7c3958dfa35c1e255d33e893403cd5ca2a0ed68fb2519443523b11d3256f62befeac296f59aee8c6722fa308db24ab94f2b497f1224f8c1779ac974515c9fab87e9125a2a722525edcf6935beb4b4c3e30e9e6a96fdb9fbc1525d79fd9ad59d40e30674d6006da782c6977d9f15882e42da4d6523f8f25491e8f254b5b70528e9d0f8f254a1fe7d19166278304d3a29a452d45240fdb08971cbb17ac3f8f25473c961c29cddc97c792240763e6b164c9c198ddcb5ee6080e2f9357e347ccd39807e291f16afc90f929f3403cb1fbb1fad5f79255e96703fdd5b792acfa32bac0e165c2008e4f6e20cab3681a49968f0993acfa991156fdbd4e92557f93dd2fa95003396651de02ca3c9628f94d7424bf993067d58f99c7922699c792236f01e598b778011c9fdc209463d7c363c9518c2f3c963099301e4b9a743058ff782c397230e6ec29e6e85ceddbd8658a39da2e756c474dee2f26b5f5c06c95bc751d69c95bd15f6ebd51e9e5c3500f50b9cec9535fcb6e16e5ad1a3bb2dc4c0ed68ff131d7cfb8788b0ee1f2160c23419a90b7b497b786bce541f53b8f8696e1d130338c17ba925c5fe5d130ebcb2c8a86593f861762185ee851b9fe56ffc50ba551ae2f8de44de4914c523a49d8e5a991a7767f918c922bfe421994ab1258abac2ed9a42499a06245ab5056a8e4ea536410ac71d40fe50c642a3553299a4ad554caa65237954aa56c2a55bfd6243efc870724d2546dca2b2ff6afc60c9d85449a9825d7df48d001d78f393291e3130f44c93487add40aed2bcc9e2ed3d1d0844da227448ef29c4c66935cbb1d32790616728c34b66807b7b5cbd9ded6bb6edddaa6b9c1c9b6f70334d7ff58f9c09285232b71b0ce2a1608192637f970f9461afb753e30799b1ea54c75dd2e270d6f9e0eda1b6d96ea2294289e72e6dd486333d74df36416664251908bc887e6ecad97fdc89bd567ef43dec47ccca7b93e1f3b92152377da6f9917cea29c7dcfa248934d0993b04893f9cb5f9126cbb2eeeb2099af83dac7dfda372a69b64fb355628b1cd4aca665d7e903505a6661b688e889d092ec76d0b730cd165925de72a297b76afd0cca5bfe59c0bedfa6216fddcf0a3968996cccbbef629232a4a49824bc69d9c7a6d8592afb881cac7f0212bedf5acf092ec0a9565af2567fd5b2efee071273c85a9f4b823f7759e070c26e4c126e9883f5b5286c935cb72e06ccb6c384dc9ff5fad92007ebdb24dbc4226d73fbcdbb9146d33eeb72d05a2b43488975592342d615e42d6b9984baee075c634796336ee2608d97a9bd2bc5419613922af7dbb3f28603355e8603ab8ff91a9fe692acfa32df95acd5c77c9e5d79599464755d478b5cb0ce3689b231f7b25f79f46d93cb74a564519acbc6ea93b7b4af5fa1a2bc5545de3c552edeca549d4ae5b9124049c95112f701a45c7fc6e2930f28e5fa93c96f224f7d55aaebbaaedb687775b752c6d9d3e79c12a742a02d6eced1186b773e9dfd79bcf319972d23970a379063f77037d268cdcad97b39fb3026671fe34dcc872f39cb3ec6eb1859e14b55ca9a32a76ec78dbe358a83d5abb1bae4a08d231c9a48265713f4c9e5831cce231752376755aa4bb97ecd9a72fd5aa3e42044510e7b4aae358a53ad5f635993bc99f1f533277943e3eb5bef67c7c1fa42628e9be8a8942d78453164686800000000e3150000201008064462b15810a5719ae93e14000e6e864c6e4c3415c8824992a2280cc2181862861000080086808111182a2b0069b489b2b05f35bcd653cf9b13d7d75e07d2645250931e000e577cedcbc50ba76f9335faffaa80e10319fbb9d42d17f8682027cb9d02aec49c0bccade7ccdb0fde0c02e299f6f55fcaea0f66efd1b536a46ef12590a1f00ccf4786ee44bca43d096dfa081c03a8af4afc3074cb0691077779a0e115e0b481f7ddc99d03e2f72279073a8c9528a6c0f0d202f28de6a640b27640b7fa5880addd2da834e8927308863eab4356c136c37c09257ad374871b46e4ae5e17fe211bc02c92328747f4980ae09caf354655a98e9318b3366d43d6615ddf2d9d1f6fc31b9a928e00e80709a1bc6f90096272efe4f11d58e7eb078bb7737fe54aa26b43425cde6c52cffa18b25f49bcbe8202d2f06967447f4f801207cd3d1b10cadc4fa5d59ec69f70d0648893b65af235598e320fcd29a23e121fef84aa1bb59475c9e48cfaeb48d4cab55e63019509f62feb72d4377a7084c4706ab15d60e0a0bd1e698bb7e550cc3b085a67f68fbba9779eec83191c3fc786f284c3180aa96388df431260c3b62793934bc37819be80b870647b3b4bf724aacf04fca71e09e1e2389a13ccf0592999432b61cfdb102dbcd7790eb93b804b7332dcca20192f9ba3bf18a83ad04e2bc9ad6489827fb2fc2091e82428a5d659bae0d5962d00efa6b67f43ab7f992177ade95ac0e6ffa2c490c8098230fabe425d912c20b65292dc06f04b5e718227f6d91f8713357926a532918854551c716df9aca939dd654dc8ed699cc69cfa1cf54ca5b0b0602c0ef6bca43e64845817f9227b6d3104e7b55191aab27df0bb37c9b3fa4dfe4d24e0cf8660e4657f709dec7ee250621694f1b0e4bd08ee091cd7ed9c1ce29c582bb43f53dc2d6c3fead2bb47ece0b5aeeb21810ad1ff0acdf1c3365fbb0ef97fa5a713351c684a1b5044fddd90e5bd13a5a8bbf7fe0edc2dceaf14f1787a60cd8e51c93f0ef3cbe81e01888e8d5cc1b7118ddd5d5af28eb322b88d5286e6da4a69f8eb525f4149463ada06dc1c647a76794c2728b7a597061492c310e7112e63560b5df76d7693d54e3ed79f1df55b8a8554e440bc6d87d3b6443593d10e872dbeb9bd990c99e398abc1b3d7cc194605317ac907cef383900a47fb55f45f7071028bf9cd9945f6373bc289f2d2fad1ffde4c5ebd05b40434d34d54984dbb040b50d183c3f62460b7caa200dc701e2022f5b19c14403cded0de19b2d8cbf70fe8ab26bace05669168b6a69328eb9df4d2ad0cc181e4e3ea2fe857052971f61a1789bfa3aea007353d5a11b6eae8e50a0617fae3fe736c9593b757984ec92a7a281cdb91d7737fa6e9c4cffbec5385d33555e8868018f509f63db48a91eb4b7f88b4b18f38c1b993aa75f4b3be8364d5c456f7beee96d64d642aeadd0f421af4279db501cf611bd9df389dfd7bfe2df3e4aef5a7be1748b7773c803474440a6ee2a3a5286cff500ecd8d508a45419b103af1eaaa9ab8e85de7e2282b73b767c837a9a159d14eff81d289115d702c1265f407e8ecaf72a18d5a28d3602aa4ecff8a60dc8802d4e70db552d30639303144ce8f70fe9c66af96d3a6866ea3870b7451b9c8ef004008c91669381dfda188d5486e2e029ee74bc679bf278ea00c8ed5762d8937345f54f05b3a0463c3a2e8dcd4ce024c5452c5b57e757d68a9a28453ab10217aacb009be226ead43355e30c6226e078220e63c2fdd0a7550bd113bdbc68b82f58cfa5de5b2705e8ad22d53ba2d090c0232c77fac02fc749a966b3da3560797ec101454e9a0cf8d60f7a1ee5b6bc12780365e9fc34b0a616a6291b681e471b1744feb5b9f2769d98723bd48c36c20185b7070c2b54bed7f803d97ed53896bc449d209acf8e56a61c1065a742d6fa53c302a7abea67f98c3fcc76185ad2d6229bc07ad60238e950d0ad75cbbe31057c70c5dc372c1b2b178814bf46f43f3092759594c1c665cdbc535b343276df343f112476840012c43b4d9f18e68a87d957e2c470e67db67186400b900e48ad993b36c09ce7b2856d6af9d7455fc9872f95e00ee3d632fa58fc415c351eb3d71347d82fd6597f18b71b5a65fba32e16227344d59197f9137d1c2d5730dbfbfeb43dfb8301fa29f5271a407c3cf5efac1a03d1d452ad1ff5b42e6d8beac2cfca98fe1aa6c638b4d1e1a63be3434aa36637cc48beb2bc7913c40a629770b36d258d287b8e2765ae7589f1a6109a58c4697e163561eb8269fa54c303fb092f0e118feb2790cbcd0b2c4d6f2aa9ac2456e63af13f3e344eca65cbe7123f266fc9dc81e09aa763d5f510494478f7551a3ba30faec546f6a4c4aabedd253f724a07b6fd50feb9697fc52d5d5a948851b2813f8392e479c99e81bedd66bc3637fc83679b6cd8113dac7a49b4e7faaa154caaffaf1ee5e95c0722895d110a3674655b6d282c6190c4997346f3ff388f17b6e480f8761acbc6fbc5f7328072554e47aa30a265dd5dcdd1c0c3eaaaee53ce24faa21840ba5237bd894d75eae50a48fc5bafbd0fe879ead58dd4073b496bdfa7cf7fe429d39b98ce5e6736b41d5212557e5ebd9ebfd54acd16b750d80c4c420bd4645fecf56470af0cc070727197146528f39ec324536d0bd36a66ad49c0b63e367171e91cb1a042a9ba30c7f49fc35eb11eb29a6d3829d957bf563e992d4d6bcbf7bc1b30994707a9e3c3baf39d4b54ad86a019602a980ed2bd3d63efa536c5f4d172e0a9c8657ad2b8a22d9bb84652b2f5fa9f3c890b6d7fdda7f153f5ec94013493aedcdd4c5805fed7fa4a47edef1852d16f79f542fccd0dabd845801803fe550923a2e7d2d669cbb83b3759c73db66e343b7bac2020d3453949bcc45eb80e36c6462f125a6f6fd2a033bc25f20ac95c4122119e832379cf11ec143bdeb6c93b14ecbc9bd4b85465175440e056ef2e93c0bae10c0fa07ecebcce386a9df9478c2a722b1aae0842d7e695012d56d5dafa257ec7ffd5a3474caf52cce753da36f9dccc26278e2c88b78d9998e7e03dc71d03f175fd1887b531feca30fcf9669169a32282bc2453e286a0dd6deacc155b7c8bd300bbb637978e274b70c25cb63af7aa7306900dfdfa45e7ddebd436baf3ee9d2fa904b2417bd676f7f6855c3c7e5fd80c6fed33baf472db14f936da8406023e111771424a2d3637ed0a84b0c9c4792828c245f905eae2d7da9a231db7a2469eb55a1c76a6875982604b843f5dcc904fde352a4bbfff504638cf3b3d0848e1f2cb89f396dfc4c97ceac4e033f2944300014295b2ed09277859e688c5687a24534f322370e907888873cd095e544f160fe17a0850854047c076256e1b3f92f973c6de0c5197e543f226e2ace78ef451084f3fb55ab803462415f9a7ef806d2b9c1d5a20e46430784aba3885947338ed509828ceeaae14402b6247b1cf418ae233d6c5884e90b34077032f9c857d45954edf99cb525fbf6c8c7a61cc43c565b92d556c245ae1bee4a357416a1a75a15111a09c0700af18310fa06c1081b9a7c5a2d25aa6c987973a06248957b2cf35ead8d83d16baee66eefb6821006ae5d453e89f6e5bbcedc9656c7d9c4513b561c2fc79be34e275c5564b28c26cb90586b30a496fd9edba1b9aa3f2b4bcac851ddd54c5a6db0dff396522668c874ed941c345b10fdfd9b782394f8008a6c2673f770f3fcc59ea9f8cffd52bc1219c30079137cf7b624c3c4db7ad4ffee57fd761b763b007030584b54daa8da7eae3a7664e06b149fe16db01fac910032efbfa5a6c7421798fb8e4533329418d5e66aef639ffbefc113dc49e9ac9bc1c3e941470fcc5818cb45438607ce75f19188af23eabf46076d6549449068eb890e7b241c250190d417a2fa2385bed3fb136c9e84ca7db7b502a9ee198339ad347de0eb2298211fd8b50272fe622f70fcf0537d9a8ebbc818993c6a1830ea649461647acc659bda021ad50853a4199631f883fe683df9210c0d89e2afeac95483e68edefcdb8cae6b37661d012f30529cc69d44cba152dc093e7f1ddfcc4188b10630045697469dabcb9ec3a1ab6c1b9bcb363a63ce0c7435a011b7d14dde64491dd713d7fc55ff02b0f1e70da77ed1748e5af8516a8255fcdc531c0736056fa656d25fec05972a5f204460d2f432b90bf899e0de5285a6f7b73f75e0bb06106995b63323764673783207919c0debc2370bf76a8541580d4377373f75f9a77b44352b8fb301c72dba7ac5d617c69d852d4d4b6dbe8c903f2dfcb145a17d0282d002462fbb4a1d89132af60dd9c6c7af17a7a8ba3e39a48a890abf6953026dcc0eaebc545dcfb4f4f3f8d9444db769d22f4cb10f6847325c0e3a355e5dc008e7555160fa5674da1c6ed760eb738083a4f649c7883f50b7902e9b8e3fa5556a76b5193c441edd2e22b174ef44dca7aaa435227840e5218ed99160bc074456e79ff26469d0b340e60780c3858aa863426511b6ffb3cedadac7b2721aca0c9e3622ed9ece8f54cc752921ed539663e220b16a81cf6a758d19c4f807e2a4817d3dc06d7346c80515d659f0098ffb80481a0977a1d907c5c1ce03892f388bfb48668735a3429e07c7ad015d11cd3cf8b4917d0202ab262743775f7edc58eb6fcc896cd30c987322ab14ca9a01fd20ca7938d4542122f8c2225ac90581c038c2205f465622404dbaf03e3597e944663b0566d8f04510be903450e0410898e1a6d0c60c39a8ddfce730ce689fd023a9135ce801fdc96fdd3394ded29a87b834c724afa55a41e2b965695b3a52f453c7d0db88687864b0fc7abaf688e37fefdeab4dc1d3983462abf6d5e3155865108d590c9eda533077aba252905619d4e9c7324a4d6ff2e7fc17dde39c71ed1f04531d7349fbefdc1ca7d994ba78f92432a250c2963732f94705f2140dc53c81bcf52e2aaa0a571c0845c647bbdf8943080a794c06dfc27d6068c82fd090c6836486e6a3cb2661371ffbc1694a8e310dd753866b9e68c646665f0c9d402ac4376dd4810cfc29dba08142ba769660b477926c1c536d8fddbf2f25dd0c80cabb0f27189b09e5fe230b7d4d08f8787aa9ebc0989e1144b15086b07c71bc49111e72a046cee89f974dc3420063385a684aa0c008446f678cbece3170b4bb6b1d70953aa90297daf3a0a172cf5c5da915fa2608dfe64cbb3cca9db2575b27d37052a45f7de6ed739e928a744a98d450d6b207fb900e4ac0fc3f725bc6d50d7298bed17ba7601c247f9e6492f13210a795ecf21dac822feef80f4bf07c7e0a6099e7248691090a0c3dd034939338f26baecd27ffe3a9205d211d40df41b99c8ccd60d0efd80addc8055e22253b6151f8f942d4d803dc16c905ab2fe45d6184a0d6616dc5d67ef66d4b0a725046c57baf55b81526c9d18ad7744057a9e01fa93d8cc4ecca650cfa853ec383e801ce07cdd0b07cdebec81f7ac838c30838d9223f03c6170e4dc2e6e754ee09ec37ad1d1cb9ead205b1d41f496aed425d2d136e74e4f0463ef90387b68f9e291f6cc31672e4d38c1b5949e47184ae252d3fd36c97ca35422cb16b8d092a25bc11952d6db73066706be9b78835d668f1ff2d8be29d892db5a95e7c3cc356f1109d6814369214c30874609ed148e3236a9fdc1a6a2a2b2525962de9a5d82f32404125158adf58d852e3ea3e74a3da276e28130b3e208ed1aec70b3379877f04528c3891d6f6eb28e86bfc19574172d7552e6fb019c3c0ef91e06f75056c03bedb037adc232e9b24e83a083c13f74c483f8b6d7256d01e88cdafba930cabf7d25b5df996a045b70282ed921821f96d61df94580ecb30483e27853d4797b7e343f4e33832b782b023adac382a3d1eef61959a5fc393cd04c5f7a303f37a1644850df503ba1953f4ef4927067777eeaad8f19d617e12805685f52709b07874525d022ba9bd3137ac9b2d1823870992737fa7275f36af62f3a043618d6cccba4831cc1391c95fac53c43fdeacdf708a2b28a3c4d6c225137dd2d78829d5b1c77f4fcb619eb1361b99a0278fbd00eac322cf1b2293784ffc7a50bd536b4e024399e8561f806c1475b975d7335b9533d0f18b22b7ed2c69a1ca0bac6e59cead19535a4cf0385545e82f7fd034727a7d6da70e0380278495165dffea2e0716dd95912d1b2bba4408c13e2ad2f552b769c99fb91fa075138a7648bebf586e8c10ade795619e546ccd5a48035b9bdba345fdf162fa7e0a1611439f8d60f8b8c55ae5d87b135c7e046d3b80e789611df554cdf21f2c3912450525c7d31d5dfdff0f4451a5a9ea95440c9ba6f653e84d1a7b3a8617a12c0334eda026332fbe4d9c9dd864d55e5f28e98a566e49812bbbcb124703c87d0bbf117b7966f230972d8f947c6fead6d836a5229d375ff1fff59db95b4562f2929c10bd63092889ac53d24d03a6c4ff7e740f04b555f4d32a89fa0dd81421aec1574987fcf53dd4f180cee78bace985865868525c106e47a43208a43f58dcccfd0fa80736b3e325a42edf9bd817587e4a98ac008ccc5306328fdee421a379803fa75e5223db6ab159d77022c628ebf53213767c19fb9bf4b251f36e3c98552d1d5c1f9de6f915e3e51a475b58ebeb7b85de7bf1085ed34b20514e4e6cfd0b52ea55c34611554ce09c684b2017646ebf5987d710aabd7a401506838dc51b02d3e887611af908d31cb3bba7bba4d3f79dd6b9a84d3b88c603d81698eff3c1f45b2594159d09aab4cd50d8557e1a831a55f466c94056426a5fdfe4c88e22b49a957600be5f0909c017a11388c79968c45682bc15f6d985355fc44443067bfe380504271fc24f407e961a420ca4305796f21f5facc68072cb170282bddd0d648461259bd33ec60eff070204f447a488ca34271d46751f8625d3a4e5dd8d0ced800e86582341168b9a571ab6440e9ae1eaa9ccf9e1c75d462e7000587485e9171fc5e0d58ddb582273fc02c11f8952dcbf6fa209e23f1101f6fa6b24f5a9065d34d54a78fec09d54c0f44340188017625348508c59e809a73e92ff516ebd77731254d9af9a9dfd6ee5ad2fd0e9fa01d04b06d8827e6080dbf0d0844967d480ec9d1c920a4cdeb5b28f8a9d6d9ca5d5989317ca2f66aef8bb3100b8df57fcf0d104b768f1efa484f5b92c7ef9f2910e256b1d7a14f667f416533809f7c1dc84a9aae53559b4990d486cf80ceb890376115dada0ef929ade1b64dfeaea919059c01ffb5416f2d3fb46dcfd6fa795f9699f0610e44fb31a994544bba23b9e815ec1ed454d4ee1f1e5069e77a2664d8a8d9de28452289d3af7476c43909b461ff2ec63c17791dbb1e926fda0a5221c8623be1c09ec07ced808cc5b4d23c1eee2f04c777464814d742ba2cd022b00ddeca24a6e454a1e578ffb58a56cf0dad3a783686e7db24f1baffd0307eff538abd0588ab4007517cb411dee4244c67fb9acf48152c51f36b1f8b82ad8c4d6875b4030f2234e0852aaf0369af967fe3dca05526f5a94e6b06269fecbbc2e3dff3f112700ff84403c067f62e162968474f25e72e866e0fd6511694a0242be4b8a9b95a9ef8e10423c3ae438e681ca153890f98e1018010955c69894083faff18ad4f21fd3693e99da150df13ca53d33be3e489190e91ec5a458bc5a41c1a6ffc7b2537bd02ede4b0a769f1ad9490f6e08a80d670de4fd0ed43bb1d0fe88644fd7e986d96188b38b88f4d6d07ccbd3d5ae539e67aa77874ecf6f7f04743ddc36019bb01900501dfa5329d42cb84fe209e228b78fdb3dcaeb9f947f547b0614f7137e3d901164a6e64a5bd6ac84fcb18a40b2b669b276e961b943f156619fb2ec970295fe189f5ff3659ad111a7c12291b39ff5c0b6609faa8473f57bf6ad7c1d239184c7ce258d4abdb807269465f438ff69cab2d2e16409e6d4d9065895e7c7a3b043e2ffeb981777a9afd8e1ca5251f46f7b7ba58ae8f7fe29a28b18b188e484b2bc194d1ac438a1c5f9e72be7b580d92a256631f99886a422aa8d5048db469ea64bd52fa58d68b6f7f8f4c4d9afe3999e27c0e811e0c2a929f2f8135e18e3fa9202b263493a4c25c58292ea66ff560ab2dbf224356abc25a9beba1cf15609069162966afc24ad14836590a89220868f23300857ad7f4c060ac8a5447ed75986a156ae88f1045ec0310df316f3e8396bf6629c819428c1da506dedc584bfb94b3fc2aa1648ae56eadc8dc7a8e0e3d11329c4a69e49adcc458726c023e5141fe8a236771a3df3ac3038fda64abbb90216982a1f4a1ca081b5506ac36b84b75fc5a41fe0280deb3cbb4d8bf3944de04fae3553d92ac910e69ee4e3fa19b11f04cf3344d24fff1794c1ebb3e8d14fc22dbb20f4ae239212e35198a3059d3687c9c73de8fc8a978b456bd8f4019fc12474f6f2117218a782a4b9b2417027616ced63b6e6f0e9d9f4830db8699f01f54205923a6fa9472a54882cbb1cd84e5b68a9f4b004d03d7e05dc0050376f86e0166d74de3d0d919aa20bf1102385b4c8bf7acd6106383f0b006f8b5caa1cf7ca4f74369dd807e1de114239b3fd2f06dd137ed1861e8cf2dde3d247ccf968b45b859ae41ca6845007d7642dde3da7a4ee01fee8c4e939452f744d6a4ca1e0032e37efe9f61ac0523a094287f319cc8ad9181c0c5d90ee51550f0f610a2a051e0717593fda0df112c7867d56efc7e0e104c06c4c05eed7f87a3999e27922d0af9c49931f12db13bf328d4015cdafb8b2c830ed600d4739284da0f3071ec0444d8ffd6a159dbc239539d019c5d01108d9b68a517abdc247d3f370ef2043c7b81bdf9377cf36d0c6dd70550f99fa49b48807d67cb46c0725ec5620fc9cf3c1dd5ba35d2222bdf1e0165d219d7175a5bccd8c72c1a32cf64d2c1ce8e8ac2873cca65fa673671f2a3692393b9fcc7f52bce821382aa7757812192ed2a4da95cf5fc6c2ade860ef6d91c71b0d8f65e1457f90cb73338250b41321f37e76b2cddf5258743b88cb6ae4ba63f3b7e93b29aef4111c11571e5501ce482e559effadc5a2e7200e0b8b72f786d12e1189de81715315e688106fe017ec8ad7bff561e810d244bf4b9d4ea03aba66039da8f25889a3f7e09f71a3dd95bbffa478d347c04855223a83d8e34fe6c357203cbd9e98edd2c9c24c06e85b3cb6078ed5518fc689fe8d038c368ed38f220147f8b53d4aeb2773e1581902e9ee4027a773a48751dd457b11d25089f5bfedd7c68b825e8aa3d1d3880f5c09c507c80829d791f0a66e0664afcda5f3778dcfa565e01fbd2567da5e3ca1402d9a2c2f31e16a78c5139b87e239dfa093465073d6d5e8c88a82a17160c7437b45b3c960f148a2acf11abfa5bf1601a3973b5c2928048d76b1ada1997d5f0359546e81673955e48115f72bd8e2761cc66aa7cc1ec09df8954df16e88a745e195d654a552e821b473ee09c453cb426c0f7e63684ff8f7b4336d92ce0929d351f4722820298eabdaeab7a957080eb9c1af6148efa5698bdc3287d39f4da80100cc54f7171b0a2db9cb6509aa22f65fe705a98eca45d32084479269ad653d696fb54e4165d4b85bf358a7834a835f0795dbe8567acdaaae4bce2c82962ec2f736a892a9cd01c19e9a528cc169b349dff236c2c4141add351125fb8e8af9d0bb1d38d3162129c6194e60e68142dac14ee1c0adf43fac8658f35ce3f83b3208ef8018e59130491c564aa27f89eec90edaea60bd1d40ef94565e4a6257c4e95542ac464de63bdc7c9e028384fd18d23c2870a1952acab767e20edb3a568ad12ff388b8a72a1a0ff10440b99c49076a497b0579853e6fcdcc12bff8ea11275501672097030b88a7003816bcad73a4ddc530d10a1ca2a24dfc22e384bcc82b695ec420d64e5c196b27e512253cc0a1a3b2c39f9da4f0d78f1747f3ad7a490bffa605983a297e5bbf57804405976e186cc0ec458fb73ae990b68bc2d430cae704b14f35df1f08f6d5602bdcdf89486774089589ab83d5028d699582b309d4e35f91c3ab7d9f2617e2af3c62ad765faa58299a2badf97d25ee3df91be278fdd6e18486beb7ddd28fd96d317cdc9bd7a223844de9c7386c8042018d264804187d41d3286fa5393b3547a0f217d2ecc37fd8da02bc4f1fc0c2ea312de33b4489e647b1d3aa70a7e2aec313514e7a0f9582b04c6dc91bfc3d0e227de83c3491250e992303352064a167dbd2536078ecf5f82fa0b25a21bbff464202f2cfcd5b2e62f52241c0896506829c55403506ddd2fc1803490f1e9c678c0f67e2d8dad7e20b209552a55bb72d766439b1a81015167a471fc439681c18ac226cf69daa871aeaa401718bded1fb8f05903f99aa706420a1e57d1017c6251840cdfa8417f71f6a1c8363b3aead9cb7f9ff671bc16f52edb7ca9c5cede0ed61b2bbd89b38ac2c8425bc50d16b54b97c0dbd4686b4b241fb95b9c95ee352281d7228d14afb355b20ebf5b4a7e8350d6098419223ea4bcd26c926da11847516957cee6c01b30f251032c7ad1c612e0700b95a0e4215ed63d375eeca3658b2f291a9dd0a9db7d9d9aef4c95ad32dbcf8f4e3c6fadc4e2bfd275440533dc806f8ff77cdfa250d61386a573f76d961aaf5f9c2d97dd428bed692b026be52eef695e1227add2b4dd742a91421c9737ac83940811ea4610f9d39df86c5da771a494ffcb1d6a125f4aed4814cf25c3d0c4f2429ad03b443e5da43091741607419847cfafcffd3e67e54000c17c8f69de09da900fb42e402c1206b87e2a117c8bb0be8ee30d1aeaad14a3c6152e94d8a6e4554b53ed87971e7e3214138e413018ef64398607d7720b05d3838d6f1d50dbd283416969c95749ca2c62acc67ea3eb178402175351250873afcfcb3b958356027733eaa6d5ace2087250538c46b80706ff1e59374c983485030e1a168345b0201b17ccceb2095781c9a084fbd224ea2d22e6d48504b8403ad66946c489896d47697e730a979ff80c424fbfb7d125881e11acb6b593148cac656d6432299a5abcdbf8bf3a2683a3f435df9097e319e6c7f72d6be20f307a00f1a3233a4ea0842a4e3ce14f3cb06684717c8aeb9506f0f37fd927691143a6998fa9480c466e1f1508ac6ee2492d826e429a291045205424e80658214fef0cea34d33c0e50ac387f55f1724d1409ec8f402d05909b1b56a2694106bc984b7fcbbfc389fcf0eacd3709e71ed00122d082de6c2651774f04667ce5f8b9f8190dcd211c8d2c8ce8806efd01fbf296677e8cc195e8c11bd20a568b2700927815bf6754e0588e3a4ab0b4d3525f47da6c3a900e0f99f1dc4c6e25c5682588abd0f16835451615980f49beb3fd1b84d1e122e6a8b3f6cef372b03532f81ff59d9f79aa08ba38f0d0281fe4079806b7728559ddde5dcd29fe661acb96486f7853c042c55b38a926ba01310bd1706ea46d645ad3840c7c081c912af152af167efb63d425dfd7bb8668a9e1fccf5038d0155e8e7547f3fac131a11fefbb3c4bbcb3a037fe329499cb5217080a38f9059d8542562872a0b86946d7ef31f8d852c77cc2a1c3ea2d5dd8b626e9bf53d4ec627295003b9e6be0cb09c6037e6f0c61f6dac7efed1484730c9e3b0469b7521f4755a4ea4df946ceaf6ded1243b921d3d1d65b913846ede9a56094aa8f5ea40958e74fda531668fe16597bcefcf65beeafe8639af771e2dbe40da67bee08ad7939b8d11d97474b57fdcbbf053e63ef5f8d90d3d3b43c1bdcf64ba4ff1f0ab83bc5d44a4ce344a4b574a21efca9a7211d922333511ac622ff5c4ee7b925c72c215da58bc11073eb0e4a265530036450128322761f1aec49bd3d864116000473c63c7a063c96bdb2960f6d07890454ea202d4befe101c3f2703d07d8c0a580455060084580690ef7340f0905636342c10a902fa48680805a51570524f30d0d4216c1e92196fe6b4df934c445d72ded16a0b8cbbfc40d9b8e6fe5dd5a9cab408ae9c6cd8fda86a089fc2db0ecaaf223f718cd80b5bd9c93120028ab72ca4f7378c291faeb2359bcafad76dc4d895cc16c3605539cdd0e7d7436c7631edef085490b13dfa002f11df3fe713353cfb98e330fc18273db849d6243bca0864b127838fb02361e0ca48bda599b6ff8067c26689310beb2b9cf6a311c1a12aa1b562c291be3d978f46b0f89e25b8a829323c5e218ff5cdf1900773bd893160fa6edb9caee94e0b360cdf484c4f61e6aea6ffa4587c8876961391afaa9e619763849ae52cce88c3fb8daaea91e6a4f95504ec2e0e23b1b66371b1bd692d182f6258ceaa1fbbab5f1d7e8abd4efcf9c89d4fabf74784887079849ee9f0d00af14be20f3870fa8e5bf899563a83f6ac5219c4ae293cc740a45176278a4c9ecdd2dd0fc72c59e6b5c27eb430b0f2bb594fca8c5f9429b2d653ea24d2bfbbea90d9a5a23d1b9d4dc35099193b0863da910fa0d19400c62a46679d2ae9fbf100ce898db248feb41cda5fa3c4e9fe043e6efc184cfee0d81d627f90cef527432f9cf9213dd50f3c976951dcac92bd335f91366336cb2eb24f19b2ad42fe13948815e695b092464fe2bb39a858f7ab364f4f29f5833ce3650f431ecdf366fe2030ac3c5e817969b2fcdfd27393a50530ebfe13dabd4de86fb93d61d3ac1cefe37c09834155f3e985e62264cf9ee123c64afb85df8c3169600d93c608c9b3efdf5a93218a5480287606f29c90b0cb9add26ecb2be81a4eac976e3fac12836ae49ee7c67897a5db7ab25874af7d39dd17bfadf20931429acdbe15df3340477d5c3f801112054d1c676547a74db918317c4f83634a6db855228330504116acfef47b00376d3b1db78b87870049ce7848b90c2048abd6ca790115aac4f947b93dd29a18df9db0c29dcbfa4ae1b2a6b8623c32fb0b45845b40c26868c1facc2c95fb5b8cc623697041cfff66fa1f1f6250e76b2905387c4106d3041cff48b43d5fafd0dc7b8d3f55abf30820772ea75d903e51adefc129f6db6b34e876b2e5593a9cf5b32a993b2cd1fbc29567e020b65fad62d2fd0e7d660f948d79d92b69f7d23065c744c31f5f2b76d2c24cc92a973c7aac841033d3aa0cae2508ed4607276aa6c811a74229e7d83dc61898990c6310da62e8e7dc8c30ae38048bd0d40351e4793dc53a26a972b77ba33ad154cc58034122c72f7e29ea0e11660e1879e8b634a8f953ce17bc6c43e9a386f3afcbc43f0cdbc4ade9febe4e64bb08655de2d75f64d9566a8461f928dfb9e56373326377443c040f20d1cc6ed2c68308accc379460ff66ff10de568ce4ead08b67681584820742c136809c4fb0784bd1f34f2fb689fbd9d1ec643ee72131f1007f61fb1bcd74c976be84c8cba617a500f23797fdeec1f0d20facc9e739344b612ef7ca24aa35c74a9a74e6708337388b88cdaf74d3c5633aaffb59e1cb3147bbe2f046de3bfbffe46e3f0b44f493f0a2506e13c964e7b4e25623d9e30b22e5219c960579e800b1ffde715e68054afca5edd5f68cd48b629c8d6b81672f80f1e4b70b8daa07537b0e57927ed8e9b541f6d6b6b5cb8bc9a1fcd5897878b303631aa2b81ad344de31f1605013b3d7e4c074bc0a708e214d9d6e8ccdf62331ad87f08ca97edf713d8fe6cebfabc7e8a32a7621d1649e3fc30eb1d939b3aa4c5329df22add6ae5e4f48573b518658bf3afb2df3614d4aef1a5a0eb9fbc2e3023c5db7ea304c8cc6b130584d9b75ee66cdc58ffa69435871adf072f71b09a6290ca430358ac05abae54a1cf857e7a10f5583c8ae9989652e5174408aee9ff54ed5c41c8e6df7c0386dfbf9f528c5a1b7e43914003d7b319ff29716ab8c3afeb3b136fedde7a3af587301bbf4c7c0751aec048352713289f3b2434be21ac50641cdc7751b2965f204184d87196412091689f0300809f2b052cdf02497cc5f20aa27d6517e19b58ee70a7594258a7f37299848db19bc11782c7fa870452f4642e614b7d7488b8654ac92ae835d6c9ecb4464cf81e775fb6a11520500b618cffc068987adb75885b7fa5d08c1bcbb24ca6304ff1fa818748d7b5d18e1009b2c16f2710b96a89643a2bd665600c88c8859cacd4081053af5b0bcce05d0889f28250168c222cc7335c32f5f134d4dc19164208f56babb5f240deeb9d29354c0ab1b60a211037e9137ffc5939e0aab3c6fea13aef8830f09b59b0a128c4f1a49833eab6debceea2b1095636b9fb60863faf9be8d884ae048fb8060ba170d962e7e9a1b8ad3fc76f684da54dd213354229d9d17c6e8bede2c53377ab5a194e5ab70f9d0ea3fa788b0882f4f4f1ecccce27543f21e6001d164d572070346804d587143b71c0783dc17fd5145ff4ed29ce07f8d429edb88cc8554d899d017e39bb151747a5d564be7db80ee37902ab2219d55cc9b2262f160c2dfca4d9729df3e180bbd9bddac9803309975201cc04c478a719b2d830c8a406a7a4f12a76ece1b88a227f08d92dc578303c61b91f07ff1c8adbada2f210bbb1607cb7adc516b278043c9cb76096f3dd5d669afc6ba0cd4bc0af312e7fc8b7476b2fa608df4e2d0b3e809bb01b18457f7bb3ce9cdce20f8a4f4e2831a9b00bd1fc59d428dfd53aff99e34927dea257396f28df11789cb5c6fff3636b1ac3e86bf42ff7e97e3b743a6192ea735b3868cc030506cce8012c7b5a2abdd5cc5ec3c34c3f00cec380cc309465d520d6d7d562d5a73454ae2b0caeb1775e2b9d526aaa6a805b95236d8105c691d6704049f9485d23951408863b40dff01da47a2f8d3939ca5fc292bcc98446cddd5ce4bc60499d3d41decb2849c60c8e19fe621012d9eac1e186879e3f4a19a1a7e21f9c5723ad5d62bf9be43dd2be305fe0d64a0a222c4b4db99f4056cf660c767d0d86fdc35f645e95f1a0ee64c07b670762bce2c611bd82f29fb0c344e81594d10dc917b9ae19eb2f42afaaa6050116613ec95c20271b90790594c9f33fde439ca25ea05c17c55f592528c8de077970f72a26c1ad9ab30b272979ea57cb74050e7c8d18738934ae581757440096ea0f71a3b83feb0998a0e7b7e41fc56ca26e23f9be3075b2b1ddc2361236105e084b4ee9907ad0b1697691d072335462c353accb16f13cd6af9ac61c97ef4daa2838a57be8a598b927e7e6e05c76b9b16052e1062d3dada0c3e037b24566e18b3990293fe6b6fdd4219e677c270d5e96af6516c7906963da9fcdeb239da77ad1d74c4962abf9b47eea4ce7a2dbccac47565d866058b547102d0fd3030b1faec649c38418b97615b4a78f839f38eadbade053e374be784ef8c7d39a54e561b063574cec0f0100c8ff477d54025b4368fb4b047261f1e7422162d94ace15a0c7b28fc3ffc317aa6fa1b3725d9a540fb622ebc7c6ec022aca2270d38e599a352e4d661bd14340e28f18c8c2d4c815cad4ebbdc3b419b88745c4d1887bccaeb06dad9959f47b911b1ae5a8c3ea74d185bdc6d7c20ab84b97883191acaca0e1c575316f6b712bff32fc687a4e09e80a70f3909fdb2e8be2c67d6e817cc2f6e2760e173b3514f81fb83c030c74a53e2c9795d1361186b06b8671ad0233783bb54cc5c9d0b7d25b9292e22f8c3feb53e60e4696d2769f88933a4de499efb30f11bc54dd5b4f9b1f0653fd62a48b779adccf7f015e72060c1775da3c87408c58c9413963fa325c9c8dc6031e06321f6212ec364479a14f2697c1c6af22c1a247b07784cacededc2f78708a0c283557907e3c0ef064d419326809bd3cc779bb5468d9cb27569f64a51dfc92479cf5f8bc691675827ac4c949d1f160e5ce77842e2d73cfc020854da830c28315703e5f6d5f93047a2d9300415db5482a562307bca699f1775d36afad1fbd40bd56445fa9599a57fcef074f2b5869223d7cf7abc222ae0d444216e1d1d79a2bd2f3ae97108930404f820885bfc75239a53f0f44bdd611b5fc44492dceb35baa953f4afb4a38a6e01077668128afe8aace974254083bfc6852980c84db8f4b3907f76b671d40fb664218c8ec8776105250a1bd920c8703d9ccf6a516f1c4d50470fc9026dbd572ae73185930b14a22386e24f3f64fa8bd192ae5c5eb5cd4d9af47bad6dc65381cc83e5382933d91740b0529ccbebfc1f4f0082305c32e09e6edf0f1081a19118694190e24d3775f5074e2609f4433d4cb501fcc0f41af890359d35b2dba2067aa800158ad956bd4aef39f6398e3332e9b82ed6becd578faff3005e0655a3985e08adb8d8de88239c1073ba30bfb25dcdcdb4b6bb03fc4fd1766718848c7e95cfa087b711f370c30bd1938248e950eeaf1bd005228673c1a88d1f4f76b459833bc26cc9b99eef94dd12fdd45ea2a209f4d79cb30f311cf92fd26f57d56f0c7955b7cd83b1896b280b313f144e7898f2849828cb9531fad8b4878bb8e2a939bf90d160fb4cc063779703ede1002c45a52b5957803d923f2b722037ec126c5658a2cacf7288aa176a40843d33c533d54cbe951c81f0a95a23fec7ae9ce063565744b02d0e5ab34dfb06b2654dccbc2e4549c2de9adf390901ff66a70879df0cb2885dfbd724e151404b27f75ae7f86bd59d57c70b0396115f54c3e30669bd44f537a1f39083628ff8acf63246b0538851a9bb174c10e89b7b24127b2b7dca3e9302881369111f8e12e3bb56c8a80a7f4729fdeca7492beb363219ccc1c05e9bbc288f84d414ec954ff42dd30f6f7bc87614fa781f4bba2f394c0e2438c395d29ed85dcc4924aa4e24bdc8f3502e0c31d48078fe0dcef7d8828a859d95e88ad069d01f149baddfdd0778e0257c6619bd8e268453a330be071909ad98fe5e539b7d971da4f020248f0971b9e0f85d85d13473e37b29e025f4561dd78654802710efe372ca1f9b5feba23a80ff6327c1ca426b6b12cccc8da0fad5183470530b1f1c7196f2896329d25f0f64273ee220c9c416cc4349c0da98fe85035a05472ebf524fcc5f2e6dc12587c474c43590338892d3a791c200e9d5033a2fef0b20d79bde58d5d2f1e87b4c41c6025eb313a21178c0c2ad7a9aacf3aeb2c3d06600bababcc4a63cd22c50e7f6810884b6aa3b8003b0ff1bced2f0bdb516de4c5bf061ef29a0e83cae93dc21b909ad9155b97ace819e733e12ba523ae6c31e48722af041f07a919d43d06b9fab1f9399b913f9d20925e3df98baa6db00f876092b4e9e610b8fa05ac2b023de54e246bb766cb0d80e01782726081338257388d9b8ecd1c66a4b4efbe87704834c68dc908a033081ac4d190ff8cac7403bc20e12de1aab0b549c8b31314243a173721439c02aede1498bc63984aeb5b502187cd09ee0c52604d29be6f5b02251816ac13073cac4a6d5775a34e8781cf1a19429b27cb2ec5f12b90ecb87c37dfab05da0faad252e89d664cf917b89162d63307a8c8ef320abaf004bae7460090be3c3a86eef761ab08ac6b1c611c9069161ac210c7c074d401ee5d4f02b3e2e19affa7e9dbe688477f74dda6aa0d4b8697999d7dc8e4eed202c6c983246d411c060013581f79106c55ac3b50fbf0c1865a306214f8d31fdbbb76caf5e88a78c733fd9e10deb02dd983ea188bdfcd32462ba0f984761c6bc29bfe6469f579d008677d9334ed064321f16ad70d42f509eae246c4cffe8a5bf5dc2d171fa25091922eda5de00fbf5adc24bad18c26c094c5f4307a3c8560f8e6382214818523c28799e0298e081aebb629e1884071e7bed2aa22ec292e09d7b9e170c460419fcae2d27e20bce60f25860b479a359688464751460b5b4be548b7563aa3f9a6f1e3b67d2814a143cf0c52e35125598993d718319e9e56eec0a9b72354902a781f7e105fb68a47a9b2ea53976f933d297a463c2e2885370d6b004a46d6c5eaac3ee2c74b4215339a6f85fe41e424acfc4fb756b15fa85f76039b46cb45f5a660ba2a071909e86d33fd6bcd70ed61fa4b95cebd5bd29e3a4e75f31d41c2cbb60e9ad1a300b9c889ba2abca568ea60dd1941aa88ca23fe8005356e7f66dbe088d3dea09232bafd7570f17b1acbf0e358893b54cfc36f03509ac887ce5f4ac35e0c98dacb48640ed1a9023c7c2685ba8dea7892d2c133353b1ec7ad4e243a6cb9ca56d6dc06c16207b3aecc5240a18fbd0091d93511680451dde121db243c7128a2e8c1f204007c650dd20b46cb7ee2b8d04a4688664b299e3789e603d0ca70db33636353ee770f00c5c145fae0856b44084618d72fee51927bc4628c4d0e4a4cb50162be8ad7c19e6447b1a3ec30f93258112b20ccb2a76e2827c5077aea8ef0fd0f8311d80f0e5285af6d877b702f541bad9293e987ec23e472db62a80f4673f29899f6e701d36d1823944abe37ee30283dcf23c0720cb5d1795eb64df267f63dd9b3beb1be7074806850cad4315c43ad9900082de85be7c0fe8195c8596e0f2c3c589979492fcf9006f180db286f8912625782bf469d2da4bee4fdf48a02884bc4010ece7c939fbdd21255775a0b8f3c1bf4222c1fda1b46b49e1867ce2d6921f526a73e764daf8aaf388691f017d5710273666141fc7cc86aaf8f38281f629735a99755adec85652f1e3e068405a09030fa01d48f7b61b3ab45174d7f67188507b29d98cf383dd92ff0b468cd8ed9af0ed001c059616c47bbad96ab31057dc24767862fcec3b1363151b5539bd872e007f8730199172bab22aa7cb0b1a4245ea08c2b2252afde8b1878f5d04ed340ba8ad662548a90d7f99ecd6d7cf92d67b098c22389d8a580f6e092070cbf958b713ce7407e8e3ffc0999252a9ee6bb6cfd883c7d252d74519ef2344622e413284f8b6d271f8ffd63d56b38ab5a4f572c8f3afdf66cbf52ee937d7168e72e2478d606b96369779c5cd9e4835a8d94fd01f4cd87b35658eab31163ad7ceb9ce4920d8bdb24f836801c10d6104bd848098efd777975715c3280fc3c762e9e7e465b7e022140f4706a31f9fbf8b3c51f392700171ff3f498d340c23f62eca8a882a1425fc3fe8b5dd909a78ddf9c0bef279c16cbf3c269d34a7dcd716e82df9d62b284e1a4f5f0a6ed4bdb936737a74c9ad4d72f2c66da7541b8b2b41cbe13315502ce9277dc84430c7acd9e95880768489eb098ce80d0f5d6b84b33e482496d9b24cd00c89e19677fb95f4a8ab969a3da9bc1c8fb6903597bb0780d48ea1de0fc4ef06bf19fe653ae17c97a18709b88d3bb0ef92cec05cc9f5379c76b5e7e781160344e9151d96f4aa224e574f105a51c58a1b3f1cb2b06cc09cc5a60d5ce380e4e676875e2d25920efbea035e4a0c85224ca80b1c4fb1f1a0f2ee49dfafd8c0624fb586aa06fcb60e6dfc189801695498ff208b9ca884c16e1493e44e90a4d64c9175c5a6c671a7645e9275bf3fe5471e34fc3d51db1d7c036a154fb7db4416ef83a7e4df4013a6f6f4612a4484e091856bdf87b3e69d357f9c0494b57b7ed42ea847b02923fd87af429b53173c15361f1de05e037f7c709e2f2b69fc195d27203a41515f863037db6751f96eb7f9218b5ae2afa6cbb07d92405f3ca6acf04916da385322129bce3f0416862273124a8d9d1aa56d11b07e1aae0a0887090484bcbd1e5a21d914782885363004539b6f06e5cc9e3417177866e5a9a1e2f54378cbdd7de662029d025b5cbd2d0f66cc166d1e9d9c76e8a77ac9f7754a8535d83f3ccefea26f410360230f20a5c43b91afe5161cf54c841f10069297fb13014efd3cadae75b7345b1c26b65080c3de891a62c59bfc3fe52699cb711d5bda78db832034a7cd227b1df93d9a06c85c836c6f94c29648b72a4bc48b30620f17b2d896054940be2a0c1d2b0078c567f7f145c58f83b5a8e2d3feb1163efd57aa75f950f18fc3bddb6317f97c95ac1813073483cec192adb397b33dc9cbc546b1feabd02a17d824433887c8d44f5f1b2b5807e1b679fcf74ffbc75abb1bb0548253939c2cdc719f15156b41498d4e894091ab1729a61cea0f7485773e33010d8047990a0dc65b874f56b314807572258310ed02f4a7618816e2d847092f29e68c9376ff06fde7afffb90b7c3e6e8819c9e9fa28e51563148d155e331e416303447406e0c6e1b822b3d6bb4022b35422874f654f7a119813ccfe751a41e1066073710fa661681099940ff9c44058c8b88b3778f4652de8e989aff5a03f83ccbb18e906d25d020ce3c9f680d1503050ea7b1bdbc6dc2c92b9da609ec09038d38cfd708ea7209bf6163a7b7629a714f08fe199cddf999fa2986e39501d89712063681e14de1b969b70f7581fa7927671fad6bf11d766d8e4c849e44d9564f95d0cd743bf3190c82c80e437d73c85be4f073e72516ef38792e362a5680acef0d96f13d29e44751bebd43b9d4f1aea49882521cf98fbf61babafedf15c64ed5baf3ffcafcaf19ea4215cd59a0c2f353ec4a22c269b8bcba280cce2d073d9c3be4475865de28081c8d0b29e8dc79bd26e374bb4ef3311d5fd7789cae2b691a7bc7f51a8fefe28a66e374be46f37141efdd318e2ec97ab5f7f873e0b965962d784ce14a7e02d3642a62653f72c6b223161d60e10c0ba7b1728eb573ac3861c911d61db0ec106b075839c20a3ae5c2da910b7cdd70e1176563d1368eb2e1e81ba3d910ad4dd16f8afee6e81b44b559948da3d83ceae61725df54d6c7e194955409c5d807d88952d1a1782914498802eda4fe33cc84b35d6833e76cbce050df26b2a7cf7cfa620bb87dd16ef0f9e054bdbdcb83d0d757c2b1469ab52804e3da20994dd4c5867fc23135c7d77dc581f10ba1aaad1ac94668570e44ed08f56d28a16c8d297953a9b3222efbd604f4974fe409c720ae8b89421101839c551910c665228d10fb4cbe975840738ab2da185a38de47fd54ab2509be67bcba95f355559907dc158837acc8e986a33a0e9e45cfa476f924c8414de721cd8b6e192f5fec13260fdfac359dcee50bccf396fd870f0d2de49dc254c61a892a45b9db9a53e7798da8c65c556d738c41512c4434954fa1485a4a3dcd554f83034caeb78772c56afb750529c560a538ed08214bd59eaf01a4c33a29e5747b481c12954247eec6143cab93dc3e5e16024c9a12b846a9b597e6504989eeffcd943902f5335fd1976826429d95a42b9400d428316cf3f06ddc77592afdeff3fef999e96c95f0fd9f67b9ef42a37e1f56677775f37b9830f617d4879f2ca529b0dcb6ced693a8bc5a7cb721ad67469d56173c2f72b3a8e36e2ddc1cfd2f4a6b833c8b0fec8cb8d0766dfd453a699e2ec6fc697a7c6bc11a41d467cdbbf4a2742c53bebd2df487172b4af1a3c171e202817505ba4378788f8ff9adde7cf0b122f623a4dd87a4d7c4bf59aa727910d7898653ec47bfab8abe1ea8eae714474d0c5d2d8b1f35b1b0bdb193eacca88d109f72a12c7ce694f7e8657aa608e287d4486f5e044c4e1f10e990b182661407f31fa2eab3034a79e79d24c3b79442701afdede36f3d3f49508ca43ccc16b3a3845d244e40dd2f3a2daa1085b43b4468a3242ff197141dd1521335fcf63dc48f21a403f02bad34e7b0fa06824bb302abdca8e4487687e354e34332d6282653a5116efc9f536710bc190eb7d88c5a3c8122c9db53cdf20357fa653bcfccc00df292bc9326f2d106f91604a420326c40bf6035cdd6d20d26c10a85991a524f0c463a6d66330ac05cc92819a0141bd800be2cd9354be9c590357091228c9ec65714db88440380c8280d14e41d85ea788db586b2508ad9a9a1ba08534eb6928d20a34c21d4c0862a6c46699da2deae7d60409c97a1d0e18fb276366d4f670203dbc7713ac39e9d244e5b08a8f728d22465a61a19f654d2c46f8edad36b41354ba72d167fabda18866f790c92f051cfa422562f24b24d98f1c5020232178227e960be05d4774d128040d3133a8edc4573be05d9f9c3bc2db03f4f55fd52716a01add5eedf37ee18a1b750f6dd2b8c530d7a2336c12acea8ff3afc367b8a043d655f347fe9ce29894cc8c7f3895e405444287f14b42f2f4b3ca63b9f17a0a0c4192528cb8650cac1c99a3b800df83beb9d4ea8259496c384ca85b89c6fca3af09b123002c0ed0add575dabe44e11dc4d0926c1b54f41037438c9449ab6c9360d725b9b1812ead8a47fe9a659ad8265042f747712a48b15802769188852c55a27dea3fdbd6fc9134cd1bb4b4135b935fca434921f632718311412a375e5bf22d8d59b0c2e0e77221489ce758d82c47396d7516712b8f637f09684247d4854d439a9a44ae83f922c0d6b761dea731b391af05d6fd3b1d3ac5fd41f54453fdd48111492496080cb2a1b1ce414beae2447a2d9f22dd025fb1de61e66fa550ac378bdd2fea8a5c7e73651d44ff4621ed6cb75a84dd90e839447beda52f8216dc79885e3670c1a77518d69728fcf1c89df5c2d00235ff7b29874d5472e488c79a482e4327d23d72e4133c93ad1dbe30a922cd1d88e2baa593cff86a6a4a9589d846f3f742c66e50b226305ca146416d3982884a0c0137d856727728166fd462bace5840dbef1d42e6be6e3f3ceb68ff2b714603f53a343069d621235b08a20e84a202cfe7f10de9e3e0446d91ea00dc1faf4e0f5f46d9c578e28e2e1ed7091e4ca928db625fedc28cb2988886687982715d959693932e808e4600150952180974d4a394c83ecd3ecc3adc11b64336497d94299a55716437533fc687c23af2a2dd28a5c91e89ae0a244723247bface9bcf7fd712c9353555a09d4555c3923c940aac94d6aeb2b5e035e2666c9073a71bd6a923ed5caf7f2c923040989f9b0bc7aea331c76fafd863e85e5b5c6d33ed568573dbd89d0211a0475d50b291eeac48c6801ca47ecfa1d7743bb552b5c99041296352fb329fe8b03bfcc1c005d0e3a66502db81b5de17f0728264ea24429204334147a613a7cc1a72ebc9673157c6f180f7fd1f12526d4402d1f6d3059a40516a233c5592eb417b9e64ac350103ab9341d7e9a7c0d17b029b88d6ee15b21860380fce52a400308a30118eac95c6f20115893c006581e49653476f800953137cfbc86ca9408f91fbdd3a716f77ce528709b67c6212f8bcd807ed6aa7df3c61ed7bdfaeb728b6f10cd604d45a7b2f078ce3633c6b97b80982898a5b610183a4f9ee4de5a6d007f8f48213885eac89ea113eec9a857c32d8188241a86803ef06a53574bc9cd200c4fa3b06b5ac6ea77bff1a15076584d4940a953c0bbd49fc47b337f63c0ae1532c7b67a0a6f572aefb55d54ec97800885a5a8d8e466e86aa014df943f0c44888c83c04203a11c91ed63308c528428d7dddaabbcf90887fc3349ee5be2e5d4729f6d8feca14f056516de1ac8821fd2887049311515797c92728b6b25ee8d0c49f3e50192a3e30ab28330b1a4bbec2e707964e7657562de74b080bd8db74d431f835604c3a092cec162ec3b4c2816d532b6db0513a00a2bc792baaaa072963dfa4cb1b6e6676db6177b8e28b229847a4714855cf6988dad01ab2af4353d6453c94fea8cd06558a4d4e1859eb485c48e724836b26f8e7ca09357dcbfd88fa1d33fb3be22041a56047c7a0b1793e527cde1ef404e43fe405bec5b0b925cea77a033de48bc4e92a865a1e773751c9c14ac2f17b3bb0203bbc866270e9c474d47ddfb9ba4806ec269a70fddd4bcb100f28603aeb33647bb9acb19849f0b879d612369190accee0367f9f0ad82cf600367f44b3b5470aea83ac076f4ea2bec116618a0d06ff8368ff32402be2f80f8cb56291f367b2711449eda2023dfa49245d36877ed75a64a5599a057b767d38a1b380690c2fab9f02d80492336c7b0597bb06d19ed213eea192df6182b8986a31df6fc494f4611ae8001a8990fa33421b05204638d63b2b2ddeecb279fdf8127eef2c91b2a8f9b102f5b401505f7016316bb99f8b9828a40679d767fca7f7458e858273846e89a73f41c38d83d5c2e94110abe8e1264b4711a02e4f07a2612ebbd1e54ac3451569ca6bdae37039217cf3e4eb7cd813f87fb4ea880e3e65a14098d8c5f0fb4672005a4fac1149b59c9cb162a131c94164781a00318967443d10caae6f15da81630b185828a592c0b1e2e879d4eb8fb8c4de639cee4970a9eae3f6fe01988b1ae7ee4b184a274f448f4dbd6639a94f73bfa26e238517dea4c4661b6b1838478f8fe7fc088ce5168068a73308e04a8cf83406603fa2e4c97a09699cc50877888af06aecb582b884ce8f959c8d138b888be037539363a6de2861b0df3016c1903fafb11645a30d819a7036dab9a05a134ab75779d9d6bc0f2e01c666843da549bb60a1c25bc219eb06530849899cd658b01dc07ac1f1c8007b461d3b229550700df9c26c40108241c732c4635c05e3e46b40c01324c1adabbaded2d654a29c9300626064006222ffc904848eb1c79a2e7edcf8ac88a7d9ec4e2602955361459dcd53872cc56c221aa73341b16a9de9538ce4dc51e3b620d69cca8fc7153b147b852e547734415c158e54773c426e28835f438bf8717236a32610442e802257f7fb920368c5a779668b1852de4d4e844091f2d9c30e29e1a9d2cc1137d50a3931c25eaee6c92e58931c68d9a6218f5228a1b0f83dda4dde1d3caf7b7f2bd701f336170038211289a8c000a272478252981e66dd96ed1021b9fa01d5439c20c57ec40063c5802089240827dc44a66b4c0092960408508aa1fb4e85021b410c58f155bf8e009438800162fa862486708415358ae8655a92af6bdf878f558b71530b161684a08a080418523ab0d08f4bb7f77fa312b68b83bd20a345c9c2abdeeeeeebe727d79766777f887636e8a1f50fef803e9dded3dd4b269b54bef6e6f3090880206182d67777777777777bf9cdedfddedee3e3528507e39a79cee3ea79473ba7bb7ecf6f6c69470051a193c81d6ed2e99999999999965337377b7fcd6a240b33ef5c73ef547e37a95d25b4a203c39527637b333377716822f5abe90deddde434c44e9a20b1b1b274b0cd5e86489292fd4e86409a04a3bc53aace3e2adbbeb8ec1b8d170aa914129a548a1289c0f252af372d7042ae50906d99330c821eb24a9a858519175560085bec80f59a7081a199c330cd26e63d65906bb2ea8ecd84974f8c421eb546769464c21c414d2bbdb7b280a215148ef6eef21160c61c1faf6902aca1552908214335248ef6eefa19eb3863f6d788b0c17dc5d87d6b44629d0893284273c410911aee0828b1a687145139a105f8bc3466a01a5cef3c3021ce077c7fc60228efefebe96daed1b49090dbf76ff37779605e154378f1c3c6af80f82dfbbbb1b5f74099f2b96b004200061c8979bb9fba5866e66ee76ef6666de961abf5b4ad9bcbb91e376408b47285132a46468552ab659a262940a88922125434a866c6e6c8edca89454515245c9909221255e28a9a264488b2b9290842af6a60a2abdbbbdc190bf19a9bd39a3dedb5dce193d36478ed9c718a7fcb09b13fb4e6686a1eadf55cf89512726a9bf9f6055fd379e1a6e40788e54b9b9948e40fde3131e55cd7ac4ffb13d269ffbccdb9efb19e65edbccdb3e8e5e7aa2cf3c8cf3e66fd3ebaa795dbce13ff2449f890bdad8273fecfa09743f880da3ced00826a57c203c47788e2ce703fbe559a1b00f08cf11066305c263132b101e1b46491e1b9e150f109e1583b102e1c9613056203c387cc4796e24109e1b1e1b1e1b1e550784e786c15881f0d830186b90280b7777cf8c40639fa477b7f76481075c2e2b6aac7097deddde43be052db4d0c28b9a6a9120a0eea489a02a438d4e9a50d5f91f10aa5a6f467aca7fa161c3a8f30b6fcc9f46aa1130d6190259a253e70764894e58fb812cc161547f38393b497a4e204b76188c15c8121e0663ed6d4912d6c969204b7270705614c8921c06fb03b2840873082ea5c43029a59459480cc330296973320b29a5942ea5c4b028a77477e95f90e6a41d83bb31b83d737641e7d7b91361d088f7507e77ad059b925b36db67e9a1999f9dc23ffce1fef06f94b1081aeecf142a0a883a3a32fff00ffff04fec41dd9fe9a4dc9fd58124c2e839cc3ad187e0e8c3a6fcf9f9913f3f3f3f3f3f510a0de3cff2ecc79f1f2643abf209fa614afc51ddb494ea242460edb2654d9f3cd05d7786ad2c01630c5b1d81915cd0588c0c75515e5e5e0b3818b4bbbba5b7f4c602edee6ee92dbd494e707777671836a54fe9d817b472f76cd9de185683d5484123f38a57bce24c646a914cb02b5ca480865ff35aaf644169d7692ee57f9284936594f2076a45949c949c04d1b0eb4c5f632716a32736b19f69cccb320fc3b0bf91e9217aadddb3ad04d4e2a2fbcbed9227f69e6dee85b37e42f41005d1f5e6337b0dcc2ccb1ee35ee22764d2b8d2b22bdd911449911449911457e2fcf9220e0757a9324ca64ff1b508449725c6e83c621224a78c9fedf9400497a56290bd9a19168bc51a8d3ea0da9fed89a2062e8c3e214b454b8827e401a39177b700d1b2713e6ad5c8081936bd3bc69f9806bd124b77ab3a25a4a56aa95aaa96aab53388277e295c39e0205c41abfe2dab09d522a3fa1b49194b947850e2c136d65ce98a4cc452f24e31990c106200809258b29d235096158eb422acd46026131353323295574a5790368dfb354c3c647b31d83f84f3f16218aab61b69041d72ab59b4dbd70e5bc987eec0fd10550e87c60f51cbb4fd85608bc17e1cfd34bc8e1593759b762880017a38e1d01312fdb27b10c0f9a3389a1cbca1d88613c0fd002b7fab8282fac72e06fb77703f44955bcdae99da630133f8f841534303901a8230a0010e783109b1e18607e000811c7418b2030c0f312b2a4e4527acbdc1914f1c61d32c26471ca9e12659d694de2cb9a436a62af14ae941eb588c0bc23fc322fe58da6613c3be1aa0920fe5c9bc32ecc42a5182a8c4571c52d9605428430299ade9940ccbd8a83d83000101ad7657946b74c2c48d4c7b6ce1b6c1a7fdce24aa2d7508d5e60bf3104ec9b0a352a93a60b3eaf8cae955ede79d42182595cc39a7a8ce8dae20505c41e6cc0c9cf261b39e73ce193dba660e5af8b453f2a911f4288360a770005540c308640314400d287ff82d7db3e69c406950cf408b354a3e1486253434c508838620682326c747132bf6827d0cc2a8f9ad28eb41bed809096d9ff2310cc32287cd30c83981beba09ce065b43acae6fbc41cb1872ff42d9043a0514bebb3ae822fe01ec08118a29a8a03fbc2a028a3752e032a73625f54ae9f202b2022173ba03cf75b76d3f73f92f7f86f77351b62267296d8afa73fd2af6b76da3ddb2c56b99a1acfb655f8862aaeb934b1dd12d62742550e60fb7e82267ea3daa51d6d85e4f214ce0a2e07ae87e8c379a68a14243dffafd13d5e839cf6021d5282327048a96312a63c99e7fbecedb84cc25894d46f5f79731d5c5d3a264f845887c5415800ddc437e4acc24bcfce06aaa5bfb7be9df2aab7b35529b2084eec5627fdb9af64ae9eeefdbfc4259e507444bf52e341a986ab846aa6f4705bb6f9be47cc8f67e1eb5bf22b2723e649868cd4d7a0a1afa14b4bd37db3c0322b6aa7c8f359a936831ee8819af3717c78e65f9fb2d8ee8dda2786366f1062ba188cb09b6752af001561bd44bb3ebebde3958460c6d937df5f6098788cf24c228223f0045a0171c583ec6c7f886f8b6d5a77d233de68644ba5ab677d00f44b62706f56873df350a442f6a1cf275ef00046d1313a82a7f6c56931328e962a036c90e136c77776f9c1821822a43e5173541b38ff12c8ffdeef470cfc9091a6ecf3ef128197b88a8705beb4a7281f945b23a5adf1d5b77e34ec6e2458e0145b6b20e83bb3b0cae885656381c48868292b8a9e14aa151c30dda292b1c8a752a0c6ab8d59d50f4331c744a8286dbb33d51c7acec2bb453469826b78d0bb2b27d33f106edba461cfca21d54fe9a9608b2280534fbe8931eb3a14ff2e29cdbc3a998171446c5155a272b2fea4aefa9fbfc2dcc0b54f7b34ed1ef2507fa2ccff211d1aafbf4fb5e72203d7dfa11b140dff6f4e0f8c5c66504743f96b6a0f1c99227aacb13f46f18ac6154e5bb7b60756f7b60a8cc3160e446a45cd2d3c362396fd4485ef28929f9d428f9947c5c7a4c2e52947c9ce8ec11d4eb0d39bcf1b67119d75be4dc4b3ecff291c503fbd44fbdef53fb683ca06157f7a6de6053f0295040b2a2b513f485748a7e9d62f9ec232780fe3e919b2fe3de1b2ad0300e099165438cdaba7da110755bd27afc33d3dbc6d558af673b99edd7dbfe860a95d9ed3a5066c5f4cf7dbf793518eccdc4a08d182e09345691fc0c08991839ce08da3b1c6f5f68fe9a7b91dbeb2ccb9e26625ea8533df3b0e9c919e69f9e911abd22b2ca8fc7125e480450e5b542eb263cfa0a7c317880bed0207a7ed1bf911abd300b85086dd416456cfab737d35fb62bc434d8589041e7ae0a943f069bfee6ce324abb2e26823518ecac8a160b4079e868abf5b65faa1dd16cb0a0a321cda6ca9fd9d1500ee9536737524cd10fbbba6d1cc463091e3a5264104e0da5942a5ff3666030ab9285d6e0d44a34712acb21c328466d2fa58d2a57a45701ed318c870ec6711cc7ad17fe0684aaf6dbf69c68c43d75c463898969abc9c42e95eab55597f311356ff4dab7ec91b57127346c09b2c9324aa3d63dfa4299da210bd5d1c711953a8aa36ef4244f3b711e0b716a063ef58f9e831a0abb639b8aec82c86fd468e5fb49dfdc87319917e3893f5a82a05a01d1cbd7b4dda4ec6ddb9e26d6eddb34ede548ab44f0d4aca1b038e447e9882714ae9acd4c7a504c6fdb0a7a145ad1d07582b6aef510a39ad5a40fd1ee68e3e639fe5a8f46a3a78975f4466a4fd01c98fd940907c673203e7192a3f0b6821ef1b31b4ead8aa79cde9327b6c25148d56b49a12d3feafeb55eecd0c89bf16660100a83fddc375745d0d081221684c17e01dc80869249ed17c26064c2a9215107f60ec4a919dafd0c1736ae5081faac16046362783b06ec32193f4d8064cc70a12bb9bc98666cb36adde6b232b2b75c59e1510d89a8fc41c9322fab9476dd7f4c0c14d992515c5e4c30345e78efd53f322e74d3803145983731ccd75fffac931850a945f82404daaca86329e5a44bba523ff4697af19a3287e28dfe928fc92601a687015d71ca027deaa863e30c7ff499be96affbc28ec6cfaf593972d4b0592f64198d8fc27c5d277bbe1ca84f1135b988600721a2a61083fd3cc4280cc693417dead74cde14298b10412e224a4a8960bf1cf2ee93ae78a385f08660d0a6f64f1583dd3795b724a58b942f529aa484919286942f48f952ee7c75ea25a7b8c01ad36dbcc383e883ba1fe3f2a8a10c7579d47d31cd181d0684212941638bafbdb0bdc0f9e028eedcf2381fd39ddd63178353d8b0eb483793152ada69739182c6b0b9f4d018381f5a90e6d2f32e516c0fcc9bde647ad3bff0a68f3d60beedf11ee661be215e7280791a4fe323e2853f27683cccffc37cece17dfcfd0b1e23e006349cab8d8627d97ba161552f35acaaf2278318ac5d0f68d82d8d8a0c582cfa0f82425348a84e61948c014e4eed9752a45011560b48bc381a7775dc0ddc83bb2a68285d73c5a7fe2ceb56bf2f988f5c0ea607ea148c8c25d0705f41800289265520b1e3535751078b53fc3222a64ca6cfe72a9c2b919cfc71cb0634f41eee0a747ee8402d1726d49f5f28228a1a9d1c2154654b3ed1b31d6f5107b7fbda1df3c3f6f5d3a75e2ac49474451dd8378b88579de21707219efa5dae560b08885bfbe2d60bfc5abe504ea9dd7da19c82dc825d928b2953a8cdad78a306346c160090a086a20e30e20e6e2d8efe1642166a83b5fbd8a7b53bba8a88a3bf49d58506346c56017ac4d70cb045d3875788291a5ff77c6198be8e457080b94242b9b5dc6a3593948c8eb9d2c1f9983873357356253d985a42426d5ce3b19590d070ab728a0c8a326caeee8e3fc09654c056f2a1fe33dcfd2524d4e3beb21212ca7d8909caa0cb0c8ea44cdf5f6c569f3cc2a0ead188fbc23d52973fd3f7cf9cce9a5b3231335bc987d6920f057d94a8a06bf337f146bfb600d3e75bdd22a366b8b0a8bfa4012bff20f20374c2f4abfa6ee6016a7c52444e754abbee1f04b5066c251faa49579ffab31212ba7d89099afd68a471949cca429ab632fce316b704f3d9c92e5c38f9fa65959050d29798a039b5e7cabf8484baf0252668b8af9e954fedef86b25357c560f7dc13b4e5c366b54b0aa382a20e197570a760beb38860bf8c1fb55ee32c5a3aec3664071e7af02102129872bbf43d64071e7a989b0f1190c0045c5cbe38a79cbfb3eb580637f2c2fe2975cee2b33fdb64a30c68b8aad9e26acfd7abf64f57d4e1df3f5b5118f5001c2090830e4376f09e9f78a39f3d6769cd97267aad7f7a315ffd43a28e51e50fc4d414e2e6920fe52954fb6384e1b17e34b1b28c15fbee9ec7599d7bfbc426fdd3ac66c220121a39f57d42a27dba097f2524a80b5f3a8286fba25dab8b57f7da72bb9c18e62c030b744612d590971d5e5e6685f1dce4c5fe6c8036345c958b17762d7d17d8ce0bb3ea82176e95da3fc393e185a0d7e26d1246b17cefcef7423fd6966d523b46f647132bedadc2603fc90ba5505d9b9b9c6fbd70c2f433ecdebe3a65fa9ac55f483f9742d225a9d4fea46bdb98f3c159e63d9daa61c3811cc85d2bdc8a17dc31b6f55c6a09092d31419dc573a0226818353a4982a7ca1a2e50ed47f5cb20209c19a559eda71cbacd629bdaee5262add5ece914ed9940325ab89543c355cd559f7a59dd56e1d64b34e216bb7ceb548df66952fb1b4a6d186ac7d19ceaf2f4770ea3244be1d4aafad4a5d5ca57b5377bc4086a53f79de37cc4b9ea54f773d5a21202c3352bdee8778901ed5f15b7f6a764af964b4a615450d421644e1be6bc61ce07cc89c39c10983387397570f5aa539ed3d1efdc86cd6bc987368b4f0dd6511028bfbf108551fcddb0317fc401025b649400361c3218360860e096c33c6d3ac818b6213e806d87ae988d8d072e0857ac87ed0607c448c530021040b4c22f3d00dc8fd9ea54d62757cf6f5fd3618bcd6a563391691683fd24269a95a2ab54d0907f5eafddd99d7d09f8cce7d31aa9919bddecb97d577bb4813b441dfdf4c2ddd9d5de0c55794d71c5568c52e7fc9199609765d3230a1b79410c51b316794174cd58fbe19c17d0ee0cc9c7bc2084a8d8871c0802abdc2bce321a807e9c318bd26dd4a5a7e4b3eebe1ec456de27d195c36d85fa84054295df07a12c04df6384838030ab33de989fe3bc38eb615f58e3ebaa7ec4199fd6f81e0251f708a388cc9df366880b472b00218c8cba1f93e346dc3124e2e0dfe7624a668b3a80f0321bba8fed973188ed7e473c9a45bdfcf0c90531f2c3f7880cf2e2cff9d82c8b01b9621983ccccdfeee63c72d4f93db7e5caf7fec4055d2a756aeeeb91fddb2d478ef871c1babf9e7bdb1734fe4b7c890fd6f92ff1ab01acf38bf18a788dbf358e3e7236c41e339c83513b84332655a1e10bf6f1c16f6745071a58058803283c1ce12ff2f0440f3df2a544e56aecc1ebc8039ee05079822354e3139c2835bea8c6273840353ec1e9a921189fe0cca072353ec149d2e97c3c68228f3a768504a3da1935451d71052b2458255ad448181657d440ddd45748449ae40de993ff8a27a44fcebdfc9020998f6514983e8389213f60de10061dc33e210c3a0e0c7a10067d981e919a2450d16f2f7fa66bdc9d52703fef655ff4fc435435b9c1b084fa9f8068188184e030845118110afcc0a817eda3639fa9d638f234f3ba1606fdd7471fefc41b9b42c04b880f3f147955f79c55ad53622efae84f5bfc41c9022a67205935b451fdfbb5de1d0901c39e33828a3e7e48aba8d6302a460da358aa63b58651b4fa8719a8f36b8474f619a55df721887d613634d2b60de38078b2d6306aa586515c0da3b6ea5fc328ad8651a31a46895e681855ec67c5be3003bb8ac1d2b4920fd89e93e2fe68fc5a4c4ceff3f449b24146c65bb6d45086f3e12244f98756391aadfe582136200041b8345ebcb1efc51b9968fbb1394dfcc8d1ac918a713494926c10713458f51f71378c389aad2491bfb6f1b66ddbb6315bf19f619a4f1bc51d71bb9c58365a914f333ff94d24748cdeffbddb525c7cb01b83a4e7838dee2ef3f2ee322fef2ef3f2ae0b16d4054ae782056db942d35c7a688c16e37fbb351834c2bb2368ffd0c42677f79ceaeeee9fb7732f37096a1235dc6a7b96755dec22700bcbb2162f66669c5665666666de8da168b1866f5bdd976952b7c6113f35fe787f60cf2ff6696b7c13d419dc9fcfd9cb6f7b7112cd861b6c60928f183c40399af6f675778fa6953003b599c6f90b33c0136450f6e8a7676f90318b58ce16f91c828dad906aa4236da3116f981687c46e40fbe5afcdde708a7b89935941b38cd2aefb9716e814113e4907c453cb2deae6d51069161041f934a20e1d9876c7cc731ec827f9222f870c8f98ce32950d83b29be5aa3adb1d2c5c6440f7e38333a6ba4777d7de9f396ff3b23ef96bded45e14e0d3fccc23b243314cbeb6fe8519a8312bf9bc0d4e51804f44189c60a7d6065c500573ce1762084ed9e089bd058a4c51c6222f9b73ce2712312fece6ace2a58607ab4b8d92a5741c2fe924091ad2796b69186c59c841415aac116231f644c15481132e55489728cb5e31b4cb8965a2d16c49d7cb67b8aaee3921099bc553c6c49c96a0a103f9d6fd0b7cf18d4b09e8ba7405f18d8dcd11579094bb0298d22998dfffc83875dd9773ba67a7f4699f469f68748f9066f589270a7952f4a2f2d21cd5eedec4304df462caa5e0c550d01dca64703b94dff4304f63a4b99751e75440f979e34a54ec10ab3624bcda59a9d3bcaa193b5efbfc30d864bf66c2e06cd6b6d57e6ddb5a1c855bec9a51635c271450e638ce879c3edc3fd03f92b6432c5d306f82f1765f432d2dcec75c195459a90de45554ee05d8345e807129c453d757f4a04e99be210ff217501725746fd86c2e3e35c49fca1bbb309ed2a79ea6e629eb05d560bc66c2271f065f58ec0b67c5ce9d6a6dba8c83bea591481add28e7a37b8d6340101b46c500add9cf28fd08a2c308a366eff2a5e7bef42ee3472fe35b1874a146ca20ff864074957fdaacaa0aead494751ee695bc66c91b02a038c6109a294b0c00b4c898e182bc912b7973d331b6920fe5958b12ba1f722bee8b999999bb65aa0680ade443e57497da8ce8c24b72141934656954aeae546dca9d7ba8a46e3625b1746a4600002800a3140000200c0a864462a168389e47b25e7d14800c7a92407c589a4a84498cc3280c520629030800000002048080d08cd04d0250a31098e106c0adf602ac265b1a4c07b3f479c66cf619dcd49303f938dfafca7d9a4e0cf4458684414caaa78842a000bb7b71f4485742a956e6232530884323317100af3d90f347ab5ed6d25d3dd9b87adecea6c5e87594b6d1838f50ec874c23f185f32c8f872fddd7c1654e8915b834d788361149ee09724d8614a5ff669be3042b66303ef4241834401c167f48ae9ca6c74a67c7a8920d7eab4ea1e00a5bd3bbe7c772a8027b441a109f57a60ff1a979461a860726939df0e9333d73a45a5bf28cc7ce17aa266109d2a74347080f5707de57c3cac26ba78d676a7c150fdb995f97406a1e622268d2133554e4eaf08f432751138e98b392661ba26b196e08e49610d0876038ea05b3b66d36c38abdbffec4bea6cd7e52abff6be78db7980bc6246b4c159fda3f4039054fbaa13da87a16497e251ae97be1b9dc79904db5e990561781ac95db096d503bf25521db59541935242e5c3012a7ca2c391f13081d14180ef9781e4ab847a67490b0b7db9f1e3105ea494c43ec97b35a9152d0d157488de65037108267c916b9c076b5615855ba622dd3188935545abb926864cbb1bdf28a6a588b5d217f58bb75bc445c9d277c2b62a270d1d8730401e87994c239774f37ada719e849b0aa7ca45f1114293072043a13516bd70242e019b4c17bde90fa6e0df44c6de4dd2fe7d9ad819e462c41d0bcb5a38945365161d4255e59b5829001cb58a832e8ef0a536e5657f85b812d4c0b017d0014afbd067a7ea0219355cd1339de245efda69020163d7c5baa5afbaf4761c11ae8fb71eeb7eac0dddd584fca5a5771bb35d56824b000728c5e02f7d4215ec91cd998b0ae469b15ae99dc845cef93009b4941a97dcd5b0c2d23b18bdf888cdb51f9476448f970e83ee57ddc0c34e92fef13a524505eb9039cff4f5d51b41ff44cfe887fa58750faf5ae01e3596f26ee1db70e9a42791322397f5dbccda2a713836379cb14ddff665716ea77292d8b4e9e554f7e04f97c225c8894c880ce91752ce26ba63492707200360de5b2e644d09401c96c73046a160b2f12da07568cb361e94ae7094eac14e33ff1f1327f0d5043b6f6dc06ff499daf706b2e7be1a7dfbdb7ef24bf34b6c43ad8fa8c7c6a346f9607759b9890bfcca2c8fa8103a5acbf917a132b5cad115edee21abc417d2b0cf9abd706cc424ef688b10128135efd9e9708117ebd80e2be805cd0952d6b02ba75653f4f08595b6e01612b12ed86181db35e6277e8e4d04543c8b691bb218553c5b94a8517c7560c5aed408ecc8aebb2d852c25a434ff78d789eca491b87eda1fe51a7fea70cfd69e4087e6197133af390ba55e0e30bd20111119d22872f15945bc8dcd49c840921c5ae356ee97a4008853bb6703d306c877cced7a48cbae4fb1006b861cfd1745771555f00c5f208d6bea81d56342c5a559a87202e6442e6cb1f1f4d44d6c4ef5b220fb6c8b98b9df34c632acc8bdb69cd5a2e1c49013d20ca2d1989b5f8221caa83896741f74f1a57d143042bed7338fed1662e548cc8eaa9276725b51ac66ff9fb3ca6a9a74e53f0b1cc7b6b00ebc5dc001775792338d703167ca57ff04637afcc1efc511585bb09a206ced03d7badccb5121e3532ecc5bac651175ba4342f705c3e2602ca293f484bccf7606b366d53d43f72f2962c836fbb56225084fba33bb0df158fe54a7a8d5d0a05ac289116689a7117c25b25dead2065322f80a8b988f6f286ed14c537184d68bdccd41573abc1e2a888004bbbd8374a21a40c6bcc0a63bdb64f0c6ecd9fb91bbbf2f53da936902efd9fbc244a339e715fa21245e68cb98b7179c97e17e31a79631f4645b634a1e6269319c36786732fdda54f3c0d38bb3e634279982086bb29b6505fa91241e57f6f741a178f956962e2b0a737ffa1ceb55610d7869c88074c401fb7d6ddc7ce81669b081ac3ff82e80010dbb671edfa8dc2c0ac9f42568468b0a8e34a124de91f38975dd31d8b902e37d87f9251968e6b03e0c09037feb751b2477053900674a24addac25e0040b8242527e8a8d7b88939264f257cb6073f84ad5e21edf43981ce1e4739ed71609fddd67df25a2798fa2ae973f1fdd7d66a183b257231a6e3af28792ec2b4a7f67b3e71506b263d31fe2f81b8a225916a9004b37f5e529fbeaf8d37fd7f243ed661d22288136fa8aab32b084d8f6602ebd85abf4f17859a1a6d876f418d4cd94726e6cfd306b9b239af46441788d0d7c49f2555abd07e669968b85fa94cfe6f957aed0dc6f166e4669b44c629a649a888653b4fc127fe51c7856c90b4d7daffc78049d12b915145f4a489f99e84632be896bc10964dfb5160c4a2743cdddd0e66257fc0899d3cdd80582aa90546a2953a637c204d658b4995fd34b4fc8c341e447ef9ee97946ef5b2c0ab7675ad68a4982ec44f87696d48f7d702583eb491b3013eb4907f4126c4e9e6ef83109f696c2b902e75d3ae37fc00bb372fe480d5aeffeb319fc9fee1f73751a634ee57d3c0673ba9ad6de7031a0e80da7448694154d539d53a678732fd46d9d4d1b783e9e283f56af79ccf7f7c66aede5e7bb8b72a826bdd32a800c855d2323e89636ba18adf62477b37ca7afe44289e369cfe15d3393b3e0ffc030060a9717248231416e4a5dd54ea7e7a2c68e815091faf56fd679ed1e16e159c16f4a077d8736d6dd81928844e3490819aed4616d2c50d588218be4422bd771aaaf8620722326f066357b98ad011137373d4b14f56aa911ebb194475a6907e0520e2b603e6815bd528056a45bfaa66fd4a58b5c09e2ee19321baea98b1575a0b3dc37ad952c8a98f6d221bff721f86ca799d08c81dcf0216930e2f94ec0738acb6670a5f1b2c656a51ad8e0abf8c30a2b3852e1dec24ec13673856974cb01b66b3b39fb4baa210ded68738352a2b051ae7708f7a958b93c010862820e4af55758525aacc8637c052982d5323c15b8481d5e20feb4addbfb5d4433b8df9024a3eea4cd2815ded4f69be5998e7e4f3c0d4e2f701805c53f55046893345514100f39279415b6dfc737add6c5dccd85ea91104f65c50bf2080fca0f4132a1aa3a4b227f7bed66a18d425866786864881bd33ecd84c87daa3f88410bfb717b7742fb94aeada8f9d2f45440683fa45cbcd1bc3def25ce0115b1a50731bec5fbc772535ede8e32bb6f7af85c85e417d05bf8510882cb90efce510ea52f9c7aba755d31ec73832a6a69b248df425b9238f738a06a7ad6058609ace5763ff3e83bc1d25ed73ab768b1d033152dfdbf581bd36c13b6be7aa90cd42c15580ece14242495366abd68c7fe92ec18d73d918ceb7dff1dc748abb7667d470a03126c272854b3abf1a109cc7b5d6b40662a34c7859389192645f652ea28e9a29907891338cb7be39e64e633d5b67fdc1b4b0b7f1ceb18311aef251d0ecb4b5808d5c21fc6c2630e3e94fc5a02ae3344912b2cbea76d693aa21e9060ee63d8825e59dc6184f12ad4f2ed48ddcd0846ef800b537fa705cc06eb542ab6feb9cdf45888d6d7b1c94e6b3ab84110af23d971f92148d1c281736754d5be1952ad08232a9b11a3f733718023fd5b3f4da237eec54a741e4233e04d0e2109f27b0fc2854016d0e450840855b99132c8f35c9a5acf934a5d984b3dc1c2d99695e585cfca3053bc1d8a71af7b9e78e0a7580c50301ef5988d3486c21aa5242188cc924d0cd4eaffaa3fd3e0f55ff3a52a1854a3eda58e8b12ea22cc85ab694741f0f382b69f516ac3fe59af23c6f948245c5add1e65d317fff90cc86767e82b42d2c2a899052f0583880cf9ca3cde22722ca20ddaca9d3a42706c3a22df5ce90cd7c1cac4ebe237e2b31a5ee1fa20b556f76a526946bbe851368177fa57beb4e77dafa4dcfe2905ee84d0ee8ffafba28244d527dc32efce6a69758a7f5a858f28293f3a8ab849b45cecc1daa0838e2b19f695ec3abe07079b673498d9207209ae34e8aec2688d1fb5554ce9bbe98b38d5f93593e67bec184c7fb71f663bf7794a3825cc78ef758fa0b0bd47c2fb540af30e076a4704b3f69e5fc9335536654594ddbf1bc6b9c72720c0dc9c85ae869b36e8450097dd5aa699a929f5a203da0cffe97cbaf6f266101ed62bb2f1eb11750c18e9ca4300f3c59e84ecccc10dd78b150d076b82ae26b8e015621b7d7f3413d2cc792fa74e75864387593e8c333d6e7097e3f4134ce689cf268cdd92cedc57b30679d41b431a2270ba4ebf5663a23de134712980ff0742a1cde92e5c29d57ff5a46fa2b94a8e38aacf19e19795a0234b58538cc332fe2e7b315a79568498f32926accfa1da0483eff9b576f45266b799d207e3a34b53eff581b207936ced7b0d60f541dbabd520c2a297a1048c3efc35bd0d681a4e3618483fe4165d0a43654045d6204c4514b74759bbaf259605f6a0cbb099b74421f57b374591c140f74442adbc2839de69380b644eb011eb7392e3244ecb94b846eca83cd26119f4f9c2a00c309a8ebcdb1b1b8652c19293be95e43199e237a03c8fa32cf85e508e8c698fcda6a6e3a36e5fa25cd46c9a5cefb35ac5474cc4752ba77024ed842e853e913680aad284d3c0c801c9c986f4e22db1d82f641827af62de415aaec0681a5b37b546ca5859917eb6af22375dc573e1dccfbe2397dc77fc62f16a1d39c3a234c573ebb963c118b96a8b49ebdeb9286e1a132233522a6e99fa5321811fb7a9e1577214f39596a3d5decf0f7addb4272f3a0f2af01669d71f48e020c07ac3d8e2dc652ed0fcc1e4d54d964d138ec40b78fccacb955802ebf43f07353191c0ef7908fb4194dbbc5d57fd6bff8ecb170c3b459fdc7fcf3416bf2b993bffee963d61a82b3911784c458c615cd5e2db83a4ffaebdc231b70f3fe2a874657acba9c1e06bfa986919edc899e9382e4c1e7131ce44e8f178bb01841b74b71b730b7d1f14922835643c8ff6696ea265b928ba35d4fda027c8be16e2d928134cd52715ae12ed3360d3674e54bc0e6bcff719eabf179f91ebf8bc1c8b30f1821ac23e7eb2887afd8b67df4bb34c12c0891249b2048ef41c44bb07b7d158aaf9704f5deec51f11c4cc5e6395b39cd32e90350f217817f902087612f461baa94d33d954d39a50cff613c591137e930f8ad318f1b51c7dc523a3563f8c78562eb54f656f0ac2489fdc900c46da01cf0decd88be42606d4a976fe8bcfd8416a588032a0fcc1cdf3721fd872059777d05f2a4856b1f9a2d9bac03fc663ce382173848aef38995272b366c37abdbe2737a27a73bc308964b16cc22b2477ac73ac62ea598e1ef0e06e3bf67ace489f0c6a81bf951a85980f59fefefc41ae02aff09a917a0797c03ddeb838def4c1dfeab7d2962fdf358eae858a549f5d80564ff2f3a4432d8ec68a8d85eecc3e5a61e1cec34d66d92bd8adc2c5338a6ef86b28510a19174437c390d1925d9185ec14e93598786055f0dc2a627610cfdb27254754449a8c418f443572bb17430a438f4d4a428e6c1a249cb8430c2608f94e07147ac4f7831f47a516799115798d26c2d78cfe10bf677213c4f701d5dabbbee62d2e3ac155cde6658bd830e7455221ccddb249555e6e13ef82849e2a0f01ff9b10efcf97e267dfa47caa730c804e92659caed3b01c6d57b4bf4eab69ae755ec2e33a152f58cbae54d3cd99dd1758d7fbadb6ca91a769511caddbf145e8d6f38ed038675e3e7ed94cde5d786ade0ca5c55d77a7233c4f09dfed7cdb620bf5a07b0ce35155abe717e267b62e6d1885d43cf2390a0a42f9ed96c18281f5b649aab99b056e11dd439d01c21d3d1e9a885653997c006151576b705f54135ee59bcc78a62f11a19cfe37e3749ae79a4914bf41fd6e7a59aee27c79c3ad2d66787b659b79c64686614ccc6067f92377852047e496cc9b7102b0a1238114610f8f937ae256005853c8fa63c90ccb8a8f3f4027cc3b0d50b32bf296d1d74b459f30d186932567871fb56cabd128eb63bfc17b24dbb0db2ca980d96da66a19569fe867a76464be7a7bfd80b01fa25ac3e57ddb0edbc59de2859c001188e83c5793e37398a34f7ebbf64712b1d1e11490f70598d55f10fd66de4e11d6c33bf6ef8270d38e41f98959163ed6cbd1c0058e77c41066e87a21a5509521881fba44514d20b9aa630a17f6e606ac9ce54ed1697242c0386021469435933f5f3a5494955c2a66553acfc2911ff89f927031e77da7093c52e85397aa1f20dc1ecc71bb2f7ac0f8181c5107cb78c2ef90e798e2be4c6a0a66b19d370647bc239ea03f4bbd3a2b00740e2e5a58c1d367f2183a5b199dadd7e2911a7a240e66f9fecbb8095174dbcce011ff2a582b64fc6638919d251662fb00b24fd28f5de0adbe5fb0202fd62d63af4eec42a470581f09608d942a24f3b991581ac90020014aba57ed2cf6be335ca53e8eabdcd43dd03bd772a0fcc3b143d5d35878f103bf97641dca858a218b7774bb0149a06a598db924383310f75c74864c729cd19a52c2ecdc44cb197f0f0492c1e70dd748300fbfd04654fb881b922c4a0e417941c4ca047864ed0068577b527dc069227f44871238a4a402d8d4fc3545362e69d7eb308c3a2092a804667f3f120add195b31c72ebbb7cd0d49a14372ebb1c0f26063745c8b8660a3faa051dbd0454f67bde298db790490317e94450c7bb9def8ca7db188267e60a904696144c3641a1f4be6f95721837b493cccb92fd69c5647ed6fd7a4c3b7baed6932791ca645f0b5ad890d7ad63e876c9b331f7ee94cc51c2da4eccd7de49c11b1fd891b40444c00e25ce81555faadc6f842dfc4c014d22a954d4a5746ce5ff9a90c5e204a7fd278e3802a013e383d74af85d2814208a15b752aa09b8adab524b496b6a46cddd8322e55852ba0f585311dca821375a2da23b6f5473ac660957596b2cc2291384667a861dd284752a96757b2bd3c85210fc2185822e4150b2b136c248a4728f3ac66db9ae60535f58d154e761ea58eb64ccbc5f8c43fae007c94e80ffa1c8d0aaffe20c9943ceda6a539afa3a3d80ffdd3930b3dc192d3ed79f4b8de81ba779be6c12b0350a0a55a4438cbc5fee74f0b568c4806c9ee6221488591ed0a9a852ac301bfb9ec332fbed4183971fca3425084bc1f255080ba9ef37e5e1d40b4a586b7200f6883104e109043e99b61f833a77747cf9f7f675f6b0a198705877d945790a0fdef8589a2a47d060b89a0212de8822bafd2c3f63c148114ea4b070aace8494d16870f7b02d5eca144014d7fd09f0673e67f2db6e1ca2a6ae4346af5275e939990822ee4db0752cec045028c9bb95d48a582ab5d4ba07b8658160611f6efa6fc7811ec81fd2a317d81baba9632bfa36cf7b0f3f4cd6035bd0586d85a450a7a2a658b1d5edac5c513fe042a5d276b4e55808fa5518ce414611cad65fd993195564e64ad1ef27e3895eea8f7667ae03f89f4338a29c2ef30d1053ff0735862fe0b9d8a85a0704643560c3f47eaae96cdda4420982a780f875ee635399213b81126b4308578dcabf55cba1d5768443b170e69050cebecba3fda815673d27acf13a607c24a41e509d88265851b7c5d3816533181adc715955f5193bf0fe90e8c03fa82dfd531954905ba05e169e7126c2442ccd5928c13cdf366d50dcf8200daabb9ee451002f20a17eb1a082f810a9e05d4cacac5bebecf3a8bac649965ac83155330652c6a98c206547970ceed5e84a98a49adb0c1d376742bab6b5aa097179a874610e1e53fe9cde4f150a86dd1015a77e0e1d4abd826707beb26c785899c14810fdef1df30821debeb7907b4dfb48d2a7db0a8a626366d52534d6f122629e5db820b57d52ed8e76ae5fdad67708c8ce7151b4515b51fe023e482bb1f182d7fbd07d404a0edbbbcffc34030966528820d04a2ba225189c225626764e0dfae2e5494856bf6caf8b5160295f62c81f37cb1b89ecde3484bda9545277f9c6ccc3482040894f14e364283fc5b269ec9a2416f682bc76265c29680d985ca098af26efa11e5bf8cc9bdb34d14f1b264b23b9dd2d0847205c29fdc05f48aeeaadc72bb0568f118379b6014bbb3b2bebf71a4e9541a299b25421ee9645df975cc2009a4cd367ed5f729e407231061cb7e98bd6bf66339c30ae4b04eb9b084657796c2d0c94b01b0d97eb9b9c47a022dd9de138313b586d04aa4cf2477d972f849c7122c84834227ca441c85d5a44629771bf2ce8e5bebb8985f8b2b62b203d3b4212a7ca51b3f25537da264003b08970c243c851ebbc6905f8258bfdbf1d537c5ff6fb4facf68524c1a2a749bd84e49ce091ae3606206959024677706955b7b6e6a2a8eb07fc1f78ec99458a422516fdbed2481e7223fb6286fc46201d56b0c953e9b45a09b70ac83b4464c00923bd24ddb43275c49ea5caa43fdbf857bc2e2912853225b0ddebcd94d52444ef6723d773155b006acf05a082bf38b1c27b0bb78b2c2c293cec075e62549c043b6b48df0d33e789f2b2f70cf3772e1dbc5e9e51f57810f9d7d3b4cd4e49bf1f671c7a88dc12ee11f31dfe74494735b766257bbf1f69ed8f53ee2b2376adb11bbddc55c76d5f881e741386b627936c45a22462d9eb330d726774d72e469bdb7d36824fb77afd7221021832772764915a89e8c54f52e50b96e38d354e27b162e944d92d77498346e253c1d83bbfcecf69429349ebe4b7e208c6d6e6bf12f76a82f577dc318e294a180bc1c5b62f13dc5e0d9a4589e4c1d667545f9976e418a26d0ec15ac755a3feacd82a4268ab290596ff84cf773b5daaf659622cad78bba496ebb74a4da67673c745d76c908dd69a3a1e70941c1445159f9dfe6361967de0fbca1ae5333740bc7c08221c07280a76c83fa44e91793f164c2a4334ddfc42b791cd8405f9a12b6792ecef386eda56bcea2596832acf121b2391963a7959f9854538fd5ee23809568b0a938bebc236950a0ebd9ef9c2a5e8491fb0eb39a262ca29ddf37bf533c23d1477a4b4b73c63296481ac171a8cb1e550019b6280e1e1fdcd7184b5d0ca06d9846bc73032405069eb51033254437452dbd0512f4b284de6292db75a0e8267291606a3c7883ac719f9e9b1b4ad224b2f84b9c88ab57b5c40b5e0695783295fd692296f5f11fb2b1ebf9ee25e502bcffbf37621144a71c2e6dfec06097e22bf06f17768134f8856f24bc809043fa3b338cf462accf232754e8f699bbf97cca4223cca9612a2542057d8789573fd7c3895e1970310b05ba9c5efb244dba14536d95edbc4c9f647bca058437a7d85098ca5472c71e7f7b63764d82cf48e914d25b5d4f281964f2b96ef69bc0704f0295abdc2c3526518a650fbee176a1d5817a73d144b2d7be984dca57b9f912f13570431eed019c22f43f316662547b78e72f816571197c75d5bd45af31db8fbf6b04ab26155f518eb63d9b3edc4d13c12f8e300881521f20c21ee72a9e2b706fd4fee78d775eea7c0e2b5321278c913f770b32d8fdbf3f368b7cfc2f3110f55202c8373db9338d48b3bd9398b9429498830aa187b59add40e16ea3de6503a8c4fb38e7b4cf75faf897fd7e14978f4698faacdbb431001181a0476c962a0406945c6067e1454427a2ad81c7fa5ee4db5661063dce5d06ce5da00967b22e654a6fc752820baa068f0f1ed7c4c72afb277e4ff6a490ecffa688a35a70cbe183a0929ec8cd4f348e48d145f45c8e3214e4864a4edf92e0c4ffdc06f1bf99d35b5c889499aecaa5117066be00e62a6cf86abc554616bef177e5b05dac1dc12594a8fa6c7f14f4df55b75f575542b8542735fef01eed7a65d2921f8584222f3cf2a1ecd668ca87115e6a9bc055067343fa61a87f0e525c918996bc067b17c34be94cb10cb6ae4a4770f71c55ee54719a1d5ff89d8bdddfce26b12cdf56aa86ebacaae8c7900a3528251ef8a50aaa7ddae0e427bde2ad738a04291b69ef8869e2d6863e0e9aa08c807f667625e01f0d5adb4e0958fd3b22d03143e6abd51f5341430605b14ed840e84a3a1b798226e816df536f122e3b36c6bd116c70fc6338cafc00681e4e1905a56d2a39039e02c8314504f6588cbce1da9a40ce8eb2223ce8ac2a2e09c7df98509e4dbf5ec27f136b06b62a95049d69d9383bf8c0762fe051bc79ad63677c85ec5e05540191b2f5aaae3e8418dc0cf938cbd00682e87d7c4b3395293e4c87f91826752fd8ef6625ca7615c2f107c48111499068bdd166e382f99ff8f0c9b42e6364774a11f7396306903c3323c3d7c850faffa4db5d1bfa2f7f12444d70d1990641f1c037e559f24bfabda62699cb12b9f8f72eb9d6ab5396704b567ec3e85d89bc5b207605c1fb2928989f966b3c1f331a3426d0a1857dce3b02577c0c8b667b2bb3d7ba04cc8f27cc17e80f08f9bb0882929530a0dba491bab9eb7520ead756064b1a53578b82d52e355ce33cf7c51bbd492f268a5086e8dbf9c81401889a2b611239528c3e90874d61c5af30df46e057929a6832f0bd344e46c62a09f0ded84cfd9898e94baac2e14c9cbd1cd3ce6d785926d347a3a6434ae94126032748000eb82488d95bd4abce2a3667c9b89850cf156d35a4487a3f6520951e6f0a3bd9d0fad17feaad6dd903a05e533883cc586f3db778a742c4d7105cf075e917c46e8d6c1d5039655b9ccdcbc4b5da2ccd3ca55e9bc3f95f0ce833bc1d819bc3a95ffd1f986d7333ab865362c9ceeb9a4dd91d5689a2bf60b85f8a7a49a5ff6198319dbb59140a8d47370026475b3baa45d78c5f0061cc188c4c1f6d8b59dc2871829c6776647ccfbc55c3c80e2e79f7e8b0dd3fd1c05a8e7ab0fcf8aff3a1ed265a5085715ad594f508c5ebda885e4ad07662643832b7ff6a906e09f52374ba5983279e8289c8fb47f191d195c8434b974de943adcddffc9c8c32c52a9ac11fc73593a016daeb5554c6567ff4c64a031b5c69cad2dc2d5d6b298bed28d922ff4ccd4d78d861c6e10c9e7c39426c7e8b3ec980478f865975ebe808a9865a954675e67c0ab3a7bb5697844d015f7fd00740dd7d5330cf664d23480186ef6003a70f507d8bb1b8d57bbb59b08c5780d7a92f7c7cac59f8ac6bcbdaf10fcbb3bcb38c71379a1b268da17e72881573f0c3b0e22cf2405cdd2a28147f3771ef6777d3c9b0337737ae155e441ad508269719521fb639cedd353981fa9105736fc670caa65285cc1314745579f7568fd36d24cc1beaddd81abe3d2e2434753b9fd95b1feec7041efe7342f7fad1130fbad4c4fe4d2b00fe1ba71f23038613d5a966e317c7f438bcb24894387ba703c2047b15374f589f42cb027034ea95f30b763355d11a4739bff1aecbf9391b95f3c38e8b9c5fcb982b1433e089472310282a13ed2880048d3b0a9abcc3ae651e603e4a8fa04218d7ee492527732a75765d1523e8be2ba666ef609c54472ccf2e6d9cc3fd2ae90765b3ed20d2b45034aaae698740c91972d7812370272607af0d87b9d96a112bc67472d5f7a084d03d2c7d36b2718401d4932813db389fa16b0ec7db10032e1aea7a4d8941e8624536b7ddfbe2523c9a8a318e4d283f26adb70c13e387753e43357f11655d42032bb43f46a213c5ccfc804e627806836c0975d700d928c8d62c55833bdcda9a5c01a9431e315e7cff696838b82ec547b85fd3dac08b198b08aad26c66b8a78fc3c149140d0d8ccf72d65867f1ac7d2c8a150ef96f8f1f163e331460c4881179418d164e514e3259de8920c2a4eb58a084abc60f1ab8b61726bfac1a0dab3abf6ba56a4e075ad5d9bcce516ba80b080081eb04a135e9fa1550b11bcb0650cb9d818f3d251ea0df0139bba7ba2991c530c17d27a203d4453d0524620ed2f6c51f7617cdfa1f91fa79cc56942189abd86e0b19653a4a97ae281cbd810a527e061e225b5372e60b7086cb78abfb48d9523d10196b7be226f4f7896451f69987ebe1474f055d4698b2c09aeb2b7ec692cd339fdfe885706e9cc4091224ffb218fdf9adb9a4907ad24a9cf67c98663045384f92726278c9a26c9897934bf74e80401475c58d6a095eaa1fb773cfc3f002b877fea65728476261a005bc3327bf9c90e5ce79ea5d343bc216a8832c2bf7eb4845a099ec1eaa50f67d85ca092cc791f63539426839340f3908d05b7d6ed312c03da087316a381feb09ca781a73bb5b131194246bd19eae28a708f05921fd53f88f847bfa40ffb7d828970025e88e2d8e86825e60b8ab8a8653c85f4450830cd81566302fb035bc2c46f745607ba1ecea939a957538991b832a6309e4f46a62733338a749a48265970669ddaf3af512b0d235f31a4deb71d383c9de50df7ce2fa1e09abf46f80c7c275a63fb56fe34f207f24a4b8e8f78db4511f911f12370b655b0f6615ee350cc17881b63f706238a79da088545a66e93a3795beb607efabc846485980aaafdd472abbecacddb3edeacfddb757c8835d313d50855b3ea2d9697e13853d11159ef265f150828fb1fb661683bdeae99723b4792320f454c813498ee3f02f83892b40cc735848541eaf41c6deab4683986bdf951dee94698b7853af4c76c5c411e4ec4a53a7459a7cca498838cbdf60597469266be562a6d3e698c868e2fadcc3810c3e306af47b67c4365c0dc5b24884f78db7cb4c3b5a47af392ab895157d3744af6edf632136d3d38195d7505dd28a640211313ff4f3d41e787f10dc379d06204da2470c5266d324f79c867d0861045b634630e149811b4b139596a559e49f204cb288081b3304d693b8f31b5eb4c555ae21674bbf83d4ab693efebf95a9d35c4885f3de983a9a56c8bee407040437a0e99776008e45dae1e8b3b03401ce8f7661676267dd0874c921548f4fe3c12d4307191f4ca80a24e7cf6246ffef38f1542cb98a6e1b1423a8f1e21ac22a6d8ffda37a0997d5889341f9bca877efba97eb4a038e6176ea558d325e3b4fa2dcdd5d2cf07f9e766ad0040d62b350c9c61099f01110ae18a9c4a1d8ded5471f757c720788bac022c852478cd7989f00e202c33ed65ff58aa07f3cc45dc432891f9d482ecde48c52b24a18c70c9936fab54700bc6208a1435416885cd1161fa9adca5d778be695e6164ccc7924e124f0959d987787684dab6540228e72660e8de1113983048fa6b2d33b8747bd4ccf9c4bddfdf1f262f83a275d53f7332bc4f6d8e37c67a0cd653b960502da974071224c401286cf89d8cac66cc23beee66c8855ccd1d5aea479c3706a53ad90d4914462df24d51850a0f0db86c594007efe4b1080fbf7dd1a72acb60ad2203974763fc08761c6af2098125f0f0835ceef65a19172a5418d20fb4d4064ca4c1286386ea595d3a656ddb59db73279b22ea84fb57fb1fa8567e57337c17fd5bd42027910b16469b715e7c974dbcbd6ba06aaa1525991e4686f40c019d5bf0fbca3f905906fac8f26c5738a799928b739c2ae44ace20852ea414c9d387e16a79518b0b921d76bab4a52cc9ad8fc9474997ef607c57cd2df50c673a43597be3994938828439c8974362d08344dc17a9ad0fc1e75c4d86f5a27dc75cac2f50681ff10811dd2c01c48d0bb70deed3e9eab681d5b556a607331d49f4f4b635836cd572eb20550b0d42f59b223c5dfd2e6c010f972cf83088de2ec96818c9fd3dc12b01e0a5aae68df9d984c877fa841d115a23509148c9e51692ea3e20eb45921a9a09d320637578b34ff7d40d3dc981f7685b12d5ba2e4a78cbe2d3a97513a434ad3aaacf6d39fd6a3f262964e840a9032c7fc44d1a38911af69b50909b22632cda1291d7c8a5b73cce2d434215d4a25b8f6ecf69502b9101a56f587234393cb7d584c27a8a5c50cfe6c6a10e6029c4bf54257c5916860336974cec13953ddceca1fb8c6641f59f4afebbcee2c2e94e697f19a7144da2fc903a5c6a87661043e758157451add1d5d2b686b747b490ea134a35b651351cfa1eb07ea57cf7bb6483a6bc07931993a29ffafb4ed8ce525fd2e7e00cf4222b740075451ddd122b330d48a8be16d37f395b44f29ed3d4a204fa6c08a9c491d7e90aba2537cdfd09370788730ac5dc808b2f2b158078c6c036424996942091b653782c2f17a3ae9e6d84ce490248f64b9478bdcd95fc1625d8b4705542465017afbe238e202760dc7d32280edb7b2e28672b010720981a1602c93da8aba489de24a58c014bb84412c8f22338c4cbdd01894562e6dd96019737b4aad195f5e27af6441e7e58835275763384c826431f78f62a66378598174461ba8496560877ba01b8e8992a0e85bb5e0cfbe0b8a1c489adcb1a8a5df113c00669d91e9f61db4ba043a1f63a3a424e40d728685f607eab893080581825e82f051c05b47cb8302ebdb6132eb8b8f40679dd67cd37694ef96b47acd33e10aecd126651560049e5e40c755d868674eb4b822676c942c4200ab41d4237a2bf05372c2ebc29a342fe58a08a10066549f768951f8f7b080e25acdbc76ef519b147f901227410c1e498e26e7ba769f3850770a601880b086e2365ed4e5e92ec5b8ef4690d8285eb734afe485b366a8baf20cc6d240d41301d4b64af3a657131a6005c0700e13929d1327338e74c50cc3644b5f047c400a94b46607fb3421de629a06c41ea6a7354a69fda3c5e3f8b89178267b1c24c7b032e72352c5ffd7a904697f718fee513c927b175d1bd12203fc3de98f2bc088e9459d026110297b0030e339dc912136be4a59846d9cebbb66e2be2b119f7165175e3d5c052fbff0c111581ec7c6184e8a4ca4d4e1caf11f7441b7303735e6fa5199a235afa9ae7cdc335d867a97380388e0e3a6970c959f17a2c47fd84b709cb83c12749c265531759319d00db3cffd276c565b6193b7a2c261f3ac211ca5d45c037f94476d43f52a7359a35597f3fb60f807f4c243f083b79261ce1c5c158185c107d3088c04c57019d89eda9a0ee3c137974a10996999198e24b6875902f68882a50e3e1c32af553a32034e4281de0cfb292b8f709974206c70d303181847b8d78e4fbc942c2d356e770af8b3357b388912e6bca6a466556a930ad32adf825ab29eb2da03d248be84478df2fb505562a61b34b6a9fe404f771ed9a398207063fb96dea60175fb97c74a98470c8982f6529203b58474595ddce4f048bec6c495c927e501e9eb8e82c78a160957b8da7a9dd55efd0868db50efba466b39143dad72083c08ad6c21c3a3a29f693f7b8a85d86ce7d6144d308a18c3cf7c723ec26c28949cfeaf562df8b3bd515010a1fbe4c8dcb52bf78b2f0ae2dd026d37c4fc6c81992b706cd772012372002f72b42cb4f0917f7661efbc05104f3a10d39283a6fa02d93c8c51616e2138ac45b35db396aad448a0d9ac6e0be3f5d4a822c84247ff43da2ab4d39891a55d5446818549e264d3fe79cd39b01e1a8045cbb704a51d322d462dc04e9268b4e9c5706d9b14dfe3361c89b6555a57e8ffae1f39bb91529b79152d7694814f691957d0896e068043514e784c09823c812c005cb984169c9098b30ea24ca1e2b0927e5712c50b6b6bf40cf431c8dc752b666e124d95bdcad2a15ea74da391b00bcf319e41da77e5200bbfe362356e61292cf8ee6bd071a792e0397a6e864be0518acbb91fac586c1d7191920d37f1dd08b542e985e5b65204cd7091e8b14c4465d3310076b3a82a064c65baf3090c0021151206ebaf2dfbe9840583bd456a22479eb1d90db5c09669d94ef6d4d6fe624d3b13f31384e1d567237407636eece1c91883609a173d3cfbbb76566699073b4a583a400acfdcbde24e422fefecc33abbf2382905d05270db12a3837c0b34b5d65912d5280605ac11693fc8425c3731c6f63b7641eb465d9a19b1a21a0aa233931d9212c32d37018db4226fbf283dddb0615ef3d28c53e02a42893b58a23c8acec793e2b8a577e4dffb68cb9a9146599261515263b38917d6233d9b4995324c8f18c8c04462a91ef35c4599ee6c4c748a38f6aec9fb165ba60f505ea52d1d10d28f512242aae747858e01e3a22af55542437c84c641db0d90e4cde3489ad66292979cc312c931bbdda7e4cfbcb2d0f27481382d221585019eefbe6facac8bb31381614814e263cb89fc2ddc89241bf54b02575613e339ee16981358eef77777c6327103518274ddb64935c5310e431af2383bfce610bb7874a1c33da5f4e438dd78978623c64355283e46adc2404a9009ba95c68b69be5e40ad173bf6ff2ee6ad4c8b4cde772abb67a794dc4cc8f4ca54a401b44cfd150e4295790aa37db011b2787afc59c9dd6046530e514076b08293838bb44be524793050f641d427a8c4d79805863ad1aa9ad1e460dd172c47289a4ea52d1e94d39204085a7b7b90fbac9fb75e6508fa60de60025647b28b5132afdce63cfeb27e504352adc59d5554a8ab27b7aa3caf3e349e8eb335e07de14e020f99e1f870ff9081cad80860148fe80a103d4ba76c1ca28e9585a3746118046fab6a84aad53d9d9596ea9032cb3120a8bb921e06e1532dd62940c0e1d0b2d03dd7eb06d124379bf24c199b4a63e7aafab8535addc4b233f1ca88fedf068d6cbb614a9de5d261412090931e73038e26710430c6418fd33f402ceb1f0924699aa075439caba8d164fc728d36cea309844871b388cae7c533878ab02c95b8e955411df645c074cbc1771e865ec4fda77de18c3f1972e7d4ec51c98dc29ab4e98a170db29dacfd0f5314baee9c48e1447973a2a24f0fddff45e40bfdcefbe545c11818c06993736a18d1832c80306a88950376114fd277d3d521be2e7d876f1511c6ee1e74ef372a5a90a23c26a73b0ea4114e59d5b6a68543295def9ef632f4cbf4a7bedaa5dfcea4777bee6b4a0b7b9bc70c15f4e5cf5d7163391bcfcbbc70205c1b37300c46014d91b7ccb5545476c29619aa29eda7490ed266ef17d547a1f51e4a8d692714ab22cfe94e1be4000caa0fa5ac24a943bf39c71cdd4955be433dff66ff90a55f934994b696c7948043df6d05b601aead1f268262b26fba135175cb57eef9e02526523e667d26c859e1fca66af7d68faddfb995b4e574d833f8c9496f5192679df8aa221e851ab4f84a7137f188de97168954c549ba3842dce976be3b1cba946e1d4d122cb8fada50ea87b47a8f2fa74489bb3bb351dece1f1d3d436ef5b53cec942fc0117f2da3c209e450b3229d5c560de03b6fa125a8ac76ce5508e308a234603b10aca3dc08dc58b9070775363834336aafab6512b28d636b290733085455290afab3508d83fe606b12e6eb94dc642c0d6bd30ceac6ced2fb3c01783e2671adf8e0a8e8a6e607259f9ad99a95c45c6a51f52faac7cf5e4af40e9d00bfd9513fc54d0ca992bbdab3ffefa5311c69f0d323d9651744a9a65de7908db92264ad0804a4962f9dbc8b99a734141260a60da93933566101feb6c6410d3ecb620b13d1cc474c091f1855d3efe853625a62ea717ffb0ce4e878895f13a68f193a24caa54f7611021356818a47bc2d7430fc59bc1cafa5a198b71f2468d38ed78c19d0e5ab0229b7826a0becfab9809b39b59c1b27be1497a6b85add4bf00377aad1a118d7dc88a4374ce488f0308d48a07e423745ceadfadcdd4c8b3f7040dbb5fabb846e1f4461fc738743ca50f5ed2d4f80ce0cdd8c4cf83dd48429437b2bffca89af53e7e9c84f9c9b39f534c4612a8ce4bc0921ba036534ca9b76b8e85a19cb655b503edd664353a2415fc482dabd89e149609fe30e6dd26dec5e91a56f5904f08d6c265c62011fa03a1cf10ba3918962deb650745c52892eee15597210d909c5668973234c97f4930c379255088138a38bba57236015ea8e74297e9fd0000f4abaab905772cf5594d0cddfcd902d709a1c7459690a9a890e722bc75aa7bd7add491b0711e589c569c223f35dfb23f4792c9ce1caccde5f0265cbf40dab8e16354e579480d2be71590f4e9867bd2650518614c898985bc24167cb986ba0071334dc62eb2e007bed66262564f5a007f276be620012cc80b13b91cba85eeb2d67f3e1e30f857ec78fe3ccb6c113923b154a8b31ca84bcada010e99a9863bd831a57ab5efa0073423af80f3aa40ddf8bc393aca0a8f37eabb03e1b7e343cd05199c5ca6dfa7a667c3d7b6fd41389cbb2d0a03b1d5a192116b14e43f8b26b4c92a3ba51de3b07288668286191049cbebb710a66bc14d311d748a92c2bbceea929ada6ea57392b2e876d40aab33cb8284f0c9def1a812769a0ed33b23a336b237c3a623e2975c54903a415b6ba1d6672ee6594ac509fdbfe7428b147df5457496de05ef60bd65331e00f11b6b80c21d31c7d873d33ba0861d16f08a4f560d1a0665dfd540a783b8b0c3816cfbb1dc6c33539619b7adbe669eae4621027d3f39d3a57cb3639e2a8c03a3a728673832a851b08901874066eb047ba81ede82b38f94f483ad699ee1e9a80c70aca291f8f6d328d3d517fc9702d611221aee9880d8943b0d12327d204f9ce0ecd826cc9d46012f05bc76f6b0032c1ff4bd484e1fd3e6d1d2192d527115dff6f88a7862bf90c81af61864e21abc9198ef3dee241e93a50538f2d4c939e230eff8be4a2c465942511855e4398bdb40b096ad6bd6914175bb2f5d5a0e1fd1a73a994a569d31945b83f3c87a388f3bc0de6286805847f314a69a11be5092b747220b2bbcbe2aa22ab90f2ed72e8b88e2abb3f664582b3ae6bc38407136d25196d4ffddcc12f537f0da7247e7f303ff272c403412d5a04a3a9453093eb349bc7532b09058c608e2461d6ba6112fd4c2f3a09739f810a28ea367f9a173481ca52ac5e087906e26587b9edb1e2c6cefe05160bf62559a120f93700d5aead0296678da3df47b34a4e87dd18810c733acc6f06c96638d54ee1fb5e54e897b01ce406692abf23316c0bc0d8bb56ac750e0e0694e681223248bf461a94f8385853213768db1d2a4c1a1c7c17bdd500ee32ea563185d549da86657fd1794635144020952ae88f8d35412c1c4054a42df44829d26e6ba256a5613ee822c2334e1b2398a7319373f1449c98909d13890169a24788aaf884aa128816638331493af2250aedfc635bf14c69362e85875f0298888983cf19a593ccf39682bbbed82388f035b66b9e11c7ada176fd5c851bb8cccd36fe6a1f11b604b540919816d9d358b5278092fed1b15922b5e192085d46f5e6ae8a153ddb994dc69ebd21ba28b32c9175d26e0bdb5628868b26efc43a3c3de369ed4ced70a918a2208d6c982416624a1fbd3980c851e223b092e07901e84dbdf43841b7861c01ff9ec2620c6bed6f8eb16132feadff9c674c2d839546e9ae4aa7a18a540bf43d4058db08b5787c08bdf2de4871048013c60933db4095c30b1d390d02f0400024e872794a26a47adcb9808d43ae6a949cffb786fdfb8ee9d67346adc9d773e65c301c3377b2d21190199b87b1e1e9f173b68ae98f65344bf75071d39f086075868e486c946ef94555ca795399278e425d321f929ad15b07e50d8dd13f68f52528d4bc18a8003898b21f14b63ca157381c65939d22ed597c5b2937ace086e547e9c72d017dfc33f2bd30ffcf542026950d04a2b58908b9da775891ff3d540e6f66d69c2a779cf79ac2658a0f71d85982d9557a6769ab1d0f51aac784c4c9cf486562b6a064a5c79e0f5e2b0a8963da996a350f24e54358301d3b9b1cf6b29bbf70883c11939afdaac6b0ae8a8afb305b1f5169b956625eb24ed92ae2f28bddc76c6b16192b93fdfff58614527549d8dcb9a8cdf4df08451f26300f8cf3d49c6e25b4da90c90bd8c73532fe6ce44cb0546bb4356e7eac60aae701d61f1dabb138c31a4135519fe177c992557844382a3c93a8133449b4cf6289e4ef883195f74bf1a819a1379e2790cb13b5880cc66a9c8a8dd8ce08a9144710b9304f1ce2f07620c3bac8538708052ce8d162b81511552a891a77e46f1901d39749dca23c24686d0f6a87a207cbc52d6e26c0b91db31e9c082aa48c18a360b101b55c7f12ed5bafd9bd55b6c9ac793660de5604d3cc52620a2ead5a4a30b4545e385aa3a4afc36466eda7310ab748b8a26fe89a8c3b91e3ed1130f53c7d6075998f2a7308e7642e370b37d8fa6f62329b916424e532ef88ceff5f828615b6d5618417b714b8950ddefd9a7174e4813a829bc91038b60e896d69ec5e17f2c95392690711088acc194ac0693be5acefe7ea5c1516be9ae361a35b0617d19119a82e5e370213ccde3cf6430dce430dba8f59a5033cb23f1581673bf9c2eec4731a0dd6586ad74dff6610b5bcdb4e3ea89d3de59dd4bba84d0235b0a563aa5180f7628bd54c299937c78308ea388cf5fec5d95d39064e352ba6f1edaa460ac6a342d95fc0c621c33fa8fe3d21b00e59c73d8d7b729cc2cf6b25ade85205d4130b50f924326a5d5af505fcd1cb0c8f49d7af0163473ba4e20e96bdeeaf4b361ea5cd6395789f7306f82916ee0435e83ef658e4dcdaac306eaad4824a653405d42c9ba0407adfc69bb277f225dbded4c88431411dd88f1bcf60e89fd83737eef2349e1cdba5714e8d2e3da9336d563868c88287f6f2ecd54020957abf1ebd08f6495403847089a24b64cd906623290d058519465e04af52575c962ee95792ed796d437d7c9dd2fb70d3c8e15459395fe23d0c8a8a611c8c7dc25b202e51d73e8c795f99c0fec6ee20da92545f0406c00d480813d01f6c2de04c8a81213b793c6e1fc654ddb761847fc7064e06f942fc2d0083bd3a4c1fa0813041190843b0f80f46a9ae27559df89a70b0516ec35066f5e5adfdb9f8d25ebd5a9bd89f56a1c259a1f8397503dc0351f167c5da3965344476e176e96a4d62fe6c871689c9671ff6184d94827ced1d49c95ed53cc5330d2da26540a19edf06c7aa2fbb2ad2bf05c75c8fdb0da0298f645da46e103e85c98ed8ab5ed27214d3364e07e3c61cb9f31e6c6d509c2aef8d2b326a93ab2f6762a016e1d7ea1c5ca64bd6b2764db4374b87a5bf6f4074ce33609f04c395e30be4b3c18d217a7d77648dec5888a1edec2deb487ddf28d07a3b218d4888913f090d17576e91abb5f237b067d9abb4bf99c658e7b91be362b5606d859de81e7cabaf6f7c04ce8cdb05e6693abf9fbb0fa8eca6c73900f3b27f7c4b6d6f6943e1fd1646352621aa499d66a1389f1c869ae03a33438f495daec9257a4898ab495d1f8420cee8f1ccef6281a2ce15c6831c1c358993bab8077fd89dbc4c117b4b9e44700f80b372a7a4ee38f527561516724dbc62f8d7df7ee505fd1ab537b17ead42958d06780f3b864695d672a1edcd30a99888cd680d37b27121c6d58560c92ffa83770a58e2f776cc7e003f1b4f9010d283b173eb1582709d00a2c995455535a0c667cb60832d9159d706aafc440ac9a4cd1d91ae23a98e8e780ec77827d86de9c02247889c85f0ab5208ad575083765bc8593ffcaa47e8c72ca9f2d4c399cd47e06b3cdbe31f5e3db72d673983642bd5844a292a4f83a07a10152da6032540d86194292889568e174b07a64b3730f74048d1e06d34a98de172b1e88da3c23303e59caf3c3cecd16ac8388d6acbbd2a26875b63ee827128c840034aed69675b62984f5dce1e737170d0b0f027fb9d0d635a013ae0252624e5a64cce11b64c0993d321b93ba1de6c84ce5b1e76b81e69bc65112206f8ee7e8fd0a7a626ecbd31ee6dbd073b4dda0d1c1e851ba417bcd930c2054230f8568ac0cf54f6c9bc88207e0022f27697c9706aa940c6de90f16ec9aa9a269dbd9dcfa48e179a9034922c12b444cc72a8cfc4e670a6b3782615e333a101e5d1f7513a33496f4ff2eeff426313951ced75bf946001ec53780f29107580303d213a335bdbf098ce74ebd4de44fbb50815ba32b12be186f1641daf5e742b6ecc81e1026a776a4619a251c25c3457aae2b5845d1141af43c3b3b5403dd609dd9ab517d17f2d0a5155c877752191aa4a633a26df8e2a1702b724802c3913f4f77a6918f85d5de4c0f996d26b775fe1ee89964257dd2713813215fc7130f769ffd5156dc9db4d9d5c0255ab20c9a691aebcb6d59a68a34ab495267366f25914a5effa372fb2fed89526948d98134f4447248d835f128aaeb954e2bc919d9075361551b844a3624352f5f5a40ec0cecbf437174158896662f94ad6455f33d6c188c8d4d2cf6321ca64026fdb0341f1b274f17a056aa0f339917b46034875a09704357421a5f03c82d2b8b60c36f98723de825bc35eb3167db37d73b3d9e3d3adf426ff3d279b0de8507ba5182e4d2b2138f0448043e1574aeaaa190291c2460e895130a04fe89791b1a9ab1c9638817a8b177224845b67011fa6ae380eb0e09338a17d4085d158aea2d4f3fcac7dcdb323a98ab2daf13b3917d5f06614135720340aadbdcc01c24b04e15176619aa1478584ad35eaf3b1d3316d5d84e7d19923838b9777d56131e1279e185ed722815876bfab57ee85a3d3586a95eb55243c6298e5b1fb6451bc3403f046913f0df0a23de83f6d727283ad79c4f70337e8e95b811afb13eaaf507935feb0b951fa484a331848a098e7bfd33765945e34c03a9c7c34b3bd585020341a44a3f8d45685181645c62beed75663101a257de1f4eb86d65817f9ffbb8ef19e28e92f341abb9d57c02ffa815332411934b457bcfb1a711f1bb8a116f0e7e542d5870d5f9db3bd8346401be71068aca83297450d588a568752d8784dad081f0604e8b9cf9c57b9278d92fd89749ab9694b233f8a2d0a45d3508a2ad327d134648cad24e6ea358ddc998e1ada96d85fb5a480078a0f12109cdbef2b9e38ab2b1f8ba72cb2a9463ceaabc5091bada842b3a1971cc97bb1dfbc4bfde17b0133186c742f56d51a9662a0148ca19eb78fa65601098397526083dd3f3882a6edb1ce7e162c383e55d15857cbba1b501084ac66bbab68ef6b1a9ceedc4354a19cf23c59381cd1d42532abcec4d596e71b3efe575845d6dafe3a90dd95d8e8463f3d5b0fbac1b1d41409afedce46cc4454e4f645dc9983738ec40fd1caa545d3c5d25c281ba00101f14e1fdfe710bef22fea76ad144a3cc3ccde53756da1d3b3eafa80fe47bf15a134274b47ff912adeb514d2604201c818b7d7a84fb795c052ade8068e8e83dba08df6ba746e932f125014878c4d979e4ca41321038d7badf1ee01419c6aa21a81d7171d2aef10dbe1e35d787bfa49908db001c5b5efa9c7b20d58ba9fb31981775406167c5df3e34740ba52b265c85ebc0a13887b4f43909bf145448a2952a6d11b89b09063beedddf0aedd1c0056bcb3a750c8a1399cac42f19586c471c929a7828860f28ec1a63eb5d6e7c70b77c3ba0f6b12b3e2dcf1bab3c572a6deb2bc77c8de46f405782e4a771fab74de495b5e2b84a06b5f5a2e602228943829425676ea9ea384de84a0964a1f49d9d621ecf2a99d7bc57290911fa28cd4dc1ae90a479315bf3eb2775199f6811cfc9a6862d52a7ec1f0e29711d66d08c5caf6605ed7ee9c65d6c9c90ee9afa1c7fae6b33fa1bfdc7488e6361f23181c8edc9729a8e62fb7a6668ae52901f72d3c0f2c8cee55b211eef76b8aadca9e84317a5ed97cf1f51f72bbfcf19b8c073cf09e85f6c142110620371a607dec981a5f5a524a383265533ab12a996d440faf49e815ed041d95b96c5a11cac3d8c2584383bf94e5efe17f0c4c7be0db428b98c28123296ec31dedcbe5faf5c5d89fb59ab6893336e9c8b9b7f883513c1e5d0c7a4233ea3ce9b9c3105469fd641af70ab8d6d22065bec37da13b6446608f2b629bd5ed03ca2cfd052aa9c055e43dd5cc1eaff8942a91d77936b8d86bce82a1608b56b117c1a9829b3ad4bd70574528ad0e10520487ddf5611751ea92e24e1132aba8a85ea5faa258ca5bf69ab5ebd5ded45383d442477f4c852e1766aedd16e545303e5604cf27daa50ca68e9b5b15b44c9bd42c7dac709a88ad7ac3d9315e5f828caccc680c9cccce0efb4bb2f4ed3a9a2ecf596af549bc30b93d4956de52960c8adf452e7b5578f390c06088d83e118d7c55f2c7c13bf4f60b1eb28031622fa67dab9c330e5ef1925e4508ba53e13da440a3fa59fc98da45327f495d0bcd27d118984878a6f87dbd438ac67dbdf11bf0dac1b0685cdf65e54cd3b66d907a338adbf69f8325837b4315bc634b699e2825990db573a1ca561324d1045918d5434caae081601e3d59b806c2aebda1cc92c9102b40ad6be25b627787784fc09172af43b8f6647a8819605f4373baf54cd37c854e4d9251d55f6bec947a61ac3dfe248a0e8ef72903b5cf4ec91526ada2b04a25795b921f9a30bbbe0e2346ffa9476fc7b8cf75c947a2726625d83deff03a96948d2c8fe9d8bc1c2558bdd6cc07e7444a8bb9e3c4389a67574628d5dea3e459043629b71664726a52555fbaffbe53e9735a0900f8a25c6a3d68e638819b15f9a2a7077378370e9182a1ef1dfd1be1206402527bc8508c4389a6f12d3b21199e287cfcd13ed02736773db13445dedf10524dcea0780eec1de97c502cd1a8c96c98731a242447624f53c638b13d0535552c15487ecf888945f4b983181aa21c3552953ab3b0c5c6fdaa7fc889ee510eb9f0ad22e55e36931b11a59ed0849e0449bbb2de9904ffc75129010d1bd867639d7d2c755130ab6c541218eec05e143a09f124198a03dffbad2f8682679f01d32ba66ba6534306c54f651f3970a34d1a0ec20b0f3fc82a5ca0476e4fcc861ae9e8ac9464397ae9e0b8b6a3b3f05ee17f3c7810c1836a4147a93ac61473bfbfa38e1006aa72cd9d8e79f0c0d36aa858e59a06a990e252c3e6bc62f6e1527573c67bd5518c910154bf87dfbaf0329873fe6452c101bfc9d9f7ddb1a5d2a9483cc9c0f5d69db63a96189d1d4553dc21837c001acfb5a1054ebaca6b897c984ac15b548a495d4aadd67224838b1f9502214ef663afdd64a949d865bb80e4724b58f2eabc4f29e36dddf50ac74417433bd01efef8715bba7190ead92a358a363422f5962ca58b1bfb996e3657ac449fd9b01b825a86acf79488758afaf2bf59d9d9136cad61d20100bd81f4d9d724644072ced881fb1fb30fe6b100a86ee9211ad669b4453c3a3a3c4a93fd28eb64019b164ec9dc9c31a2ef3a19b45e5f915ca3880bf79758a3a0dcd6093a2be1c7cbc56348cb2122554cb07acbc072db1567bcabcc8dbe09dd5e9ab66bb2240291def3f38ef906cfc726ef8187b15d5203eee8c919370ad1131da67ac3271eb340db67a26217a5c78cc413b99f3aee381d662ab17e1f24c024f8269c7986cce8546edbcb05d9d42597d992b05360a3be8a705a4d4281eea13ee8f1fa4d249fcc6534e31ce4a5c4f1bdf1972bbff6c9ee8a362b2911a1a5182637ccbc8f878cdd4661ad53765d15703c6116f000f5626ed919ff04e727a2a1deddac0005322804cf57cb087aa3503b11519add5eb8da6cf4d003df3461512f4e88c78522f984107c8f17e8349a5d14b2e0f1ed7121688929799d0d71c360db180635a476d2b7b9a14f1b97a80f6eebc0c805e262c0efbf18f4b51e3ddec0932a732fe93f27e708dedafacdb3b3e6e35e55437ea51a8628c81558be9a7c9059a3622d7d83ba25a293fd08cd502d75a1bbb115f4893caf8763d442152acd11836fe311ab03cbae40a4478e0ad4f6e00766430bc986da5859675ef5504321ab982831e2882c99f1ee5ac3b5b22f53e3e7bca408dc5c190d76982cfcc8360d0d8c6a932bf900b7f124e9a32c0284599c61243637a26de01e05ec173f3795e268fa6d9a73628a2704aecc3ea365f6928a1291f9fbafe01a276760401889c544f39d689b5068f9534e242e98a3d86119ab74114e6447a1e0eed8108811b748a6d373a201f283c6231faa5b5f9eabff9f761de5705307311557f2aac8ac9d56e92f2787d234a758a696ec3c069bd012f44ecde96585aad35aef1b1da2605b3e45e1047af4cfdd4a2718ac8429edef44e3c4b3353c3db39d09e42047f8f13acb8caf5c0b83b63477ecb4e9746862cebe7e9ef3c42fb2c1006917ca6a9e1af9dd01f8587afc5d57f74840126a47134591a91a6cc6c4168c85288acec1a91b8f4e82780d66c8d54c714c19ca6ffa7038758981b5435164861898a5aba8ab2e3414054b93eea4468918d23e2ee7a0b0860d5e17fd6d35b61a1faf3311a6ff3513a16dbf05440c45d1f049543783653c14c5a489a447260d5114649e5fb6cc752ca2b20b2d5edb6bd150701f3a9dbd5e07a285a77e2a17fedff77b37de870f9fe38710e79f8e02f4b06f86c2bd73b5d325f27412a9b6a235d3c822877e1eb8c6740282062464bb450dc509e3021732b7d7c5bbe5ad0c7749f45766ee8d628ebca65dd4afc3bb25abb81e5cc30a36a12cf6b36cde9ca752d53925b7f3b45b07a8c33487de9995ec5e2672209b12c9190f2861172d34df14d535e6651b618a1a705f4ffbed508c748de034ed42bf3d91966182224c7447262a567242b60a51812a36bfa138ebd5cf8a61da7ab5831fd080f4cbacb020922b17901206d96e01bf9f0574ee29200258617e031386d4c4e37bef426967da18ad7ea3a3ae579f5213a920ea2a2dc62d2bd5926b05cbf95b4f7c72749ad7ed94a83e3c04675ea15d1399a1e69cfb15d99ba883a350f4f0d2e389b3cd1adc7b3751dae635700f17fca191cc2e09f092cb84363745c855c7c57386a849d045718e18011f4b59df2f0d429be28981f65a74029e502bdf7c2fc680f58133daabf31d1dfe8ce66322f9f7af8e54e5d94e40937ca403ca754e7b5930d81da5037d9bc9c866b3d92c7a6b3c50e1cb1a77bc4f1c1c48add941c8490381c2a8ceead5edce5cc02fdf38d4220f1ef07a42cec68d385871df0e7a06735c257c6657132f17b9420c8be6ac9584ecbd37915bca2453920196072207cc07140b94f5fd3b5037d062fb552b7f2cd644a1546f08edb56e3064fb552b1653e8287ab51fb20ec7d3938a9e501a455db2075e484cd02744a42659e4200e82817ce0cc3ce2237aa4653ef9b1fdda69fac4e168b20fcfd1d1d17322f721fb89ff56a8f7858d48d7e301834023f37b26d7936324c535c580f9f16d98313ea91a89d5c8ac3d3993ca2c28ac5da5ad5cb5893a0a9ce97f393244e8287008d47ee12c3487a679958ddebff7ecf619b1910c0ee8ed28515a8a562487d861a37282c4f67f371f4322b66f084f0b1d24b2b4cf7c359cd482a2e0bdb1ed7707ce0e9b20209f1a04f9fc0021b1e4081217b22aa04c502eddf59697a3280bea4141bc1cfd2fe7e538504e572102bd0de4400c7017b222fb892f241901bdd34a2c68fb04cda0eb2ab01d740417244a1a0073b808ce0c81f6b78f63d1bd2cb0ba23a4d87e1c3b1e11330e2ba0ddaced844bbea7271d662704bd13d6f880cf0a54dec3997e1f5de339efc5e7ef410066c14717e48f00b2fd2b1cccdb0f55773dc3fe8f5aa1545e500ad8812ab2fdaa15efe06f511b56774e604526f179fb715269ddf5bc2cdb94b5c389e0c50824ee03db4d73bd8993a1264750a4482fdaf1fefcdf77d75a5014e8f5264d604d9be0cdf407310403db2d44932ff6d297838de0cc0ff4cea939134bac375962fbb9c9500ff11227bc845ffb55c7da6133df0bfd324c509926d0db422dd44465fbfaa1f77442f5903bc1da71c3a48bedc7a91233cacc7ce3a35fe8373541617d37cc1dff0d059a06be0d5b9ca679416ca890b5047a59ac97e101bd28942a26795e984499efc3d7755dd705e1c52c351ece48bfd0be8141af431564f2dfea7a904d01dd9e406f0319d5cb947650031d6924b63f2b25815efa7290f2211b1979bd7c74f4eae52d9772974babd8db40b619007364854570c6a404bdfee33f0ee4415c544524f9e173e2fb0b2ff4471ed06e1f1f9f143c0c9a466503274c6b08d1abd98c7db658cda2382e94bf4b810f90cf0ff561a07ea1ffb1404fb5852428c7042a63047ad9a79d64454eef4ecbcf4b918b95c74817caf3f85d0b3d2d09b2fd456274875aacd7cb3e8f2b62842752815ed6e2e5ce9af7995a171b649cf356b7836cffe604aa3150ff3410031dd14840bb835a6812b1c4f66b27f83f7c8eb3709c7cc8f043efb40dc316433df3a2502254952fea8c7ae65918b638ea9967af4d375c821fd19d5109db8f4364fb7d54415948bf8d01fe71485b5214ca6ac0cbe9b4259b11e8fb37c335c5ffdeea50c4c22cab218e3ce1ffe17364f417fa4b3fd014fc6771d4553bcc16ac235e06e86b09a1c4b684aab0f4877de017a04b8ee52f6a9a0c260623a4f497a679569a1cb1cfc6c0248ca6795698788901892a9a261503125d7c54c7911551d2dfd596f9465234290eb9e98ed3f4717afb7e1caca97dc6a56963b2875f8a711b13838699e8e9f65898cfa026833bfddb6020840fe459585332bf035fcab8c77097da983f7132dcf31317e3df167b6c8c41a1e460e9e340182ac542d7782a43a1cf6524a13b22276d6e9ea5af695886b1233066c6321309c31e23c166228be19af4faf724fcc8e14067fb362e8aa88da63184ecdc90190a3333f638dd750de74052c1eb61c47cfb1dffb93de97b2cb3d46c6bd83327391c9861d3fd495d8cd0fd278c30bed7989f5d1cab830efde7c78ed2b2a2c3f983948a2aa25a6891fae3d8418ea1748dd633fef15b4af638ddeda0affad5e5736e3f703a3233cbe7975f8af1bb4b3176c72c931dabc3c162c5eb23ab696275d2cbcf481cabc3d1c97fd61f0ed60524d64922aaf1f8e2ab71057a57b7c3d13492fbd11d3f7bb14492c47a565957d0cbb2114298c3d3e2520913f31715c3b9807bb09532c9e334e46ec0000e449a866d7c0ae8d8e0a36be8c76771ac615a390866d788bd7e4e9e189e859af61bc735044376fe659f0bbf647ad2c31e4b32695fd2fee26e67d9aa16e78cf6396b6a72833fe1f603f3487a12370011507f9e1cb3c64e20b18c65c39e9947c933237d06996b2ac8b3fc409ee59aba7ee7afc7786c2471dbbf2d468e8866b79edccdc1668f03b5884241adc6384540e54792855afcc9b9bf5fc0c6273df7ecf4a9713656f7cb0410420899e85108af961d9bbb6195dc59b22bee3e85eed1dcac837540f820ac7fe3bdf75a0455dcddddbbb322dbac507eef6191cda669d83cda3df2cbd10c6b77f7912cc2c7b97577f8de7baf9344c7b81b06967de84010bdeb5116d9468e9f0e089f1bdab6469a7bb48e1b5f7208a0f59df8367bdc8dd3448837d893f0a12834fa0d7843c6e8f344f6604df49c8c357e33f02307b301db0f63fb5f0eece9e61410c54775343d5afaf38cc4ace4c139d29b91df037549f758b9f949cdbe55fba4608d74e76858949d21632f4ebe19f80fee603b9e6c0c1be3ce519b619a73269b69259b699a739bcd36cd39cd6625cc6630da45b2d366319a739795d1a1cd6668282d55a346c96b7c39e0c30fd8e6f7020481ed8f355210426f41d69f9918c040d8c2cbc2cffcb9bb7bf32c053d2c58b060c182050b962b57ae5cb972e5ca952b57ae5cb972c58a152b56ac58790f0b162c58b04ce13d2c56ac58b162c58a152bef09e1439e0416772c58b060c182050b7dffa290c17fabfb8ae0d1e3f1be7830889ed2e5092c84ff155728c7ba03b35b1a57a80ff9101278048f58043c3a198272a8113f6294d2fd69fede7bef69cfdf7befbd1791903db797edd7c25e7d4d22a02af649a975f5ea1e638c1e6574f7e841f0a24108d3dd10763b1872c2b894eed121840e55e0ec1c07d8fa334cb37b7bb787e1b1fb61628c578c1cd923757f24187e3142edc1d73fefbaae4b9baa192dacd65d37b7a6d149213333bf516bdd9b7c87793d2c25373bc312bc27b8d8cc94694f64b13f1c0687ee4e2794eaa58c7172befd8811a541a1e549f981c2cb27e189efe3e3c31321333f861042c8eda9212c849f1f1a11b98101a24204919b2d78dadb0a691f1f1f1f9ee6227bab223e3e3e3e58ad083a9b86888d851305a11512b3c40a0b6cec571eb6b0f216fdcc833bf41feecc27c8f865bcfd809ad74d869efe21f3cf649123575fdd1c5f728fc4acb342a9eea796d0f957ab3192838f8f8f4fce0f9fc3403002f8fd3523d5c6be3d4be8ebaedd23113e96b3afb54496a12674d3329d7a7d96b5c61bd7e7429c91b8ced410146ea91ad09deba3bd6fb35fc1c7c1da61223d89547586b8bdea497542a99a463e8f421e534250f96cefc34fd58032b4699a9210cd02c13ab0ba4415f6be85374b50b1f057382c7cdedef6b61fd14677cd310ef458f9977fd6ba823d1b93a0602b69902feb85f07f70bd3d52be8e14d7691a7f1d0804272b13982fc17ca65d9f9924174bc3be961351c895c0248d846dacaf3b1d20885ded638df3e15f3146d36f5cfa382b0fb4db5579da6e7fb17ac177bbd59d59b787d75fad5ed1ca9df9dbac3bf0a3dd6add6888165606828f4418b2292907f3120766509b1465678cfcc9c9a75c4cdd62e405c3d1dfb8ecb3c951ce7b667e36b5c7698d7b4eb9494f284e65a43ee8d03a1532f77e776ee7ca87df834bf7cf1a5ea5d893a4fd75912ed245ba48a4d7b81dec5d17e9358d94658fd31b86fd45b7d7fe9a7e23354cb63dcc46aa3c6fbb5bd30cfb2f715a46babe1fbbea6d534b2038416407605fcb09244efc580ec2be56135dacbf97b5d46304c39e8164929d4bfaf83d58c95d12bb952efd79da46d2750b335f63205887343d562a61a512f69c84dbd9fe6114c3e49bb037491d20e56e0f76877f7bd3df98a7f3ef29be7f8d1276e25ecfd02a612a4f5bd284a9314af572a0e7c6549e7ea63a25f6fe1bc74068bcd1a059ac5ed2aca7ee3893a7ee6a0fdc43fb42a289a3eef83cdddd4451134d9a48620f605f8b092dddfd5bff167f7b06da7a8bbdc5cfb2fbea7529b7f79bdc36b9bddfe4f67e939793ec38b4524a281f06411d60f677fbf72f6edb766575e3913b0da52cd58df426aef2409bd5db8391a63c695b5f4d98d0820927363365d9dbfc71fd3c6d3926945801d8d7ca81172d1601f6b5723064b397f9a5733d035d0f5ac8ede8605ffb33b4d77b7fb9f2b4bc34f9aefcbe590d7d3d736daf959ef2407b7d7c53dd2a37801f2a2b0fb4f26f1cc00fcd2a4f5b1981c8ba239b5479dac2dee19742f865d568d0acd7cb0f2b03c13aba9eba2b755cc2c45e2552d20c42a269f11fecdabc6d406a3c069f85dff006d8651b57e3513264626c3d46e03bf68e04831bb763fa1e2c3f1573b76aebcb2d3cd7dbd6f42dd4db5cdb74b9325677ae373d77576a7b8c3084fc38f0cae7e91dfe1e6cc34bfe067fbbaec45e1e0cc3eaf524b663c088c188c188c1187f7b4b8cbf7d04238c30b68cbf54c654dd05dc3565e3e563cf823537dc781aff33b82f9d626610c2df1fe448b8473fdc1e076e2db1afc1993620a6aff12513f637b4f077dad4bfd473f5bfdea877f60d0c6eb05ed6b08fcf828dd5fe1b2dd4d23f5833e31face18934aa58ae6d7018c0a1bbf834b4f9da80d4f8ed25d7afe2b4674e665f837b36c5c5a751ef44d53b67d44b65d44b4bac8bb2b1be8da9b7b330f576945e73a3cccc3bf3b1a7ee826bfb57e5e571ed1bd318c3b619aed7be32d3df385a53a497f5e3df70512efca5ab0740e5a0c75ebae52f5d3546bbbd7c6daba6f8f1dd05d89b5ebbb02fd5db4741db5fd4a576abf23dc85d700306ba8b7f495513bcd8b7af95441325b458967d2d25a0d856122d1c081959cdc29f2fa5950321fb827d2d285b6cf62c405f0b4a14dbd9d78212645b50aad8ccf2cb18ff623ed334989f756e9fd5cba56d03527a7f3ed234d9f3fc57fa2c9bf57a36dfe7ebccf76c07fb1eec7c3d27cf78c66920b8873f67d3f4d33467f57ee6e975a71ffbf9f72fd77bf518e9c1f26733bb2198cf49ecbd9e87db99df8365ee4221d06acce96441f6f234b49771da371a6edbf9d9054b7fb1f7af978ffc35025aaa7c4f32efd59ff972e5696b9a01ab332a4fd7d48ccf84cce7e7671d06e21efc59e58bfaabc4993620f0b9eef0635f72feeb7d7a85a5126f1b90f93b1b0d9a9df57220db7e68527846d350a0d2677f71fc3238ed6538983f6d25aea4d35d4c0c2e857df6cf9a3e26864d1508ee717dcda0d5fef2c0cb03b5baf357fb8cb37fdb0cf3d9ab36430c29a6a6e65f3cd0f25f1ea899b4a7309c7747eb9baadc80f0ef94eaf671a341b35c2f077aecfcedb5d7d1b4f7ed873669e8ec2ebb640c8e7922f3476941047c848184d8d2dac19716951ef81498738a0bbdefcb8a7b823d3386b1900e05b4f8922b4454f1f124458cc49f58048330b4bb6ce609d48f7417dd2709fa110ff2c0aba126c73857e21ec581403b4b77f1db28c90dfc664cff483f30474cf53df06a8a3c1e4a9aa649d3f44f1fd80436fe7b9783b011fbcb4beab5ed2084b358edba333df7197313ab303049c644329948da56c48817347bf9b78ddc54df0533ae1fc3d2111bebd7ca64f51cc7d68d8c8c6e1bb5112b9b9327a98522526e3ffe33267d12e84d6047bfa50ca8ff8c5afc1a1d124ecf9f5a13fa7cfbf1afc33db08f2f81c783f4f127f068eac7d5df590380bf9447cc9b991ff39583af7b3ae63bf75e0e0070f357dc7cd224658f9132794d1700e002ac159bd5492492d73b9f7f879f3e4e63dccf76d243278ef39fee805c0000d755f44cfcec0a22213972e4fa111bdf93344dfd9ad5c7f7218fe244bec4a1b8146fe24e6cac9ec493b8121702d236c583dbf93a98ecac33361a4c3f2b0f6cf9698c2345608dfb40eeefaca169b8ca909176ea633e5538ae72fe133fa505bd5ef5368a340dc9c6c7eb230d24ac24c53c6c4dbfe340ddc59feee2e368e31b61ec6bfc15d7443d1365feb611071d61201f1cd80277d9b680d522ddc5f937b21a9ffa22469ae6dd981bcacaf8f7647cfcc9b95077f17d63962d026b74e04cfc49aa37e2abfebe2f36d807d4cf97db10d417691a1a1fdf08fba07efedb66bdd9c37c46bb8bafe24eddc54f7135381a286e06c764f532b82ff732121bffc4a16c0cb7c305c3518e87ada9cad05d7c9d58aa7792b46a64e307c9e244cd1a6327b9ce8b6e7400beb4a8404901a14505d14f9cd2a2224a6b8a93d614252da2a11691921497860f1241a2088b580b248244cc85c8aa9864713e52c9806addf50f3ea2f66df1b73427c1d2401a427a3f72d8c5892cc50525c521b4888efcfcc2356849b1c576d132a8c019f6b5a478e22e448c08296770c5d8d792e204b3740592530183d91285512b8a1e584db3af15c514cea40667925c40ad50aa57fd0a27888fee7c10041314558a1044e18b143889c1cfd050163168c2959f2a6e2004143e40824251a5403d4a8582353850361394c8c2022438c1c9175d8ea4502288265daa60832fec25007bb57c00642f17ec15054e10a6a2f5a0c85e971050a81dac57354e10d47732ec6b45e1c45e949d42193fb3669cac806261ec6b554194da8164d974c73bba63a2c4ea8eafeb92f4fa560f98d8eb6a596185bdaeebba64ca08191d768a0736fc46d027650ad605fb5a52b4b0121dc87b45580218562001e5b13181d086767e42c5132c4fb278e2c59110d94f67eb39aa6780e66cbb60fb6dbc175053f80e91dd08bf8effa669701bdd6bdf1e7868c87fd334b25ef5f54c6cd5cd45d570e343061b6c6c90f0e2cdbc22bc9967635dc243028c37f3ec8b5b64f0676f3f6030a8c1edc789b583c2681066f6a1b8878dbfd2be8f525e0ef837a268bc1ed9bf4fa96cdebf1b01eca039ec03c05756db7835f486634e33de878f1b1f37383ee01719f48b8c61a13bed8c18311ea76fbae6f988f190c298ea756b7a99b7b9f1d7a6351a345e0dfba0de3f3e1e2dbcbfe41e36defdbfc67bbd446e3a8804dd6123e53d0d6d7ecfbf51ef0d7bd947c8cb01df061703eb80afe264e09eb36183a16d8d4a8197033e8a0bf272c04f5da1a8f77f3c9a896e66c8e8e2fefe685072ae1ed2a8da3f58c37d3ff74c4f575c102754bc99675de08238c1f2669ead5c102759bc9967358ee38238f1e2cd3cdbdc4571f7054188a7ed9c362926f4d9798a115359ca11bc99778237f3ec13163c91014be98ea83b58394a0a0b9d9e03f9c8a83b16cb491196f0669e05c30a382cdbacb5d6de2b2d71a921be637aaf279bc5c4f9d341fe1068a53471cf6a7cc4466c746f6c0d3a37dcdcdc2061052723707202272c706e4e0dc66b726c6d89f3d7f5bf8e7bb6eb6b0b7d84a038acfff53278338f8889040de2a47b34d37158a7f06995281c05d6bc1c7b33ee596814657e141d98857f368233ece587d3d9d4311fe3e0cb319ff466e6c37f818c9d3fa104dce76be51f1f37ddc1bf9105ca599a862fcb280623b25c160cf2848837f3907833cfcabac49b79419e40f1669ebd9ca47d6c745d92fd708f5785cb40d8c726783cfa0bdbd732da62ef73148842f9f4f10efa249d907b4a0bbd9af526c1e3d14960f60de5635c8f85d3a5c7da2923a87ca0d7c6067617d95ba5e093fe67049c859c27fcf3dfe3d1969482ef3d0337c884c4cdc9fd3073606c8c61e36fef85f8292ae8f517955d1c0f3bf04ccb1ef9751104969faf19380b57dde17a9e6999af8d06b7bd491eda5e281ac9f169f3dd4bf6568f47f3db81ca410160c3da938ca4217d3fd748fd7438d7d3cb21f3ba7be3146fd808b7c7e9e67898d6bf833535c099f853ab77c61ae09c57c642ce36d7137d1dfcc9696fbeee75af8344ac53cd76571d8c58ffb7d5221bbaaba1bb2804855ff4ba481b2069e940a8abc123a0af850589bd5a07524f6c6c524ee6b479c0162b8441851c1cf1450ca29430743044145e00c10a51b090f3fe65c0a15f7e0485d2885bbfae182b8fdbb7c9ca43432e9ef4bffeb8d3dfffac777bf7c3ea49e29c97bbaa5445f418af2b5e33cae8fe504b947c5d9c039a80c678c1b8bb9452ba747fd121840efd0b2dcac265f69a86fdb645ea3d1a4b2064cfa5cea579969d9a869df49ae6fe9926a5eca2e80db786301420352623fbc5457708358fd16177c330d81906652fc6a8d3ddfcebd44de3163bec7b2c151534ba9cae095d9d5551412f9d539b4d5454d03b55535453bc6a8a0b321b42d810a2a351c511a0745cc0c6011416c0212339c4424546c821a31546f4bea0a20418510fb688432cc5141dc4a176550e9e5176c513492cbf11d6e5c62971a889888338e5b211858f432cc51429e210d64568bafbf329b4bf4027ee108975f7ef30ba0612f5977a894029902876ac2ff6d22f32acac10e80af0081c6a1c4059c18443b28b6c6243894c10464405bbc2094c07d8152c95178ce87d41c50f18914c90c7e8f19aee369d143631b60a0a270f03baff73e527b42e72680a39050a394565630839c442454de490d6c54745e417b5a326c883365182c01afa59954982f06ddb8882f2bdb1b1f1099a441642d50ea87b6bedd55dc504f5245818abea4acf30bb63af1ea5ac44d83f254403cfc87a77100bc772485d3373baa8020846d60b1f2cd4e88988bfd6f222082b2dfc8714caaa3a1d415513d69c5a5d8461210972504d6465a1ca08faec3d39777a23e8ab615fab0b2258695fab8b2cf69e2ca99d087e5a1366ee6786b0a686380f4fa16ab25245798296550b08559cc8deef9f5102bd7ee45ba86a09bd7fb404aa2ac221f36342318e646fa5c4baf5990566b1fd503b350d95eea6f5aa620289b2d808e1c3f02fb2549265a61417ee66950d5436500d51c5c8c852c166a6b7520d41999999354f4d81c2ef77d58eea570cd96be3831ba15e55831f60c1022b508ab802143d4cf83829a205250062d2b2620a5b0f7e5cf8c11693155e6c9b7d2d2eb88091457b352b8a322ba890b870828b2655b4b848b2dda48e503ec8544df314c24cd360d5e1b25946e25259a60581eef09348cf05d01646566a9af640aed7bec742987197b10cab902349ad36370a72ab8a83d534f077bcbb177f90bd55962a028d97641581c6a979f187711aa5f253424e317ffa1ebb4515bbc3bed60f8a669089f132df63b38c3512c635cf392de460fe6d7c918a5892f61897fd3c7d0c2e262b71f14d5ca95b350d7dbf9e524ba90d467866409f7d5b70d8de297083897b16fe9c33aba921d7cb0772b911cbb454e2604a6d618a4cdea8a9042b6972279a0a02bd2c164e0dfefaf9cbef1972d5a7ba9ebf2745043a7fa5ba56ec836191bd08f46fad1f0cd9edb3f76867a040fb79eee09f690f7174cdcf93cdedc631313dc9f4441008b5b35ebe9a14206fb7f7ad3a94f2e6c77fe76615465abac030809008398942a528cb16f7d2346de1930d4af17aeb0c73ee00e45952dbde7e94ea7b3a6ef6fddbfb36240436db86b4cd74e8c1b721fea47adf661bdb21304a77a7aad11848699a6c88571825bb88f8fd40c8f6f726845afed93a309402a7bc1cd0ff2f48053a815e08e5a3cf92ecf5134c989afb8988de0bf2e371c9eb4a0d5dffd75fdaaa3ba154524a499b6675b50f9c4aa28bee8ff1eb9b42aff6eeac512dec50d0a6b97e7a8fd10a4b297fa4a2a01765a5ea579d464f8f1bc0cf75315f2cb998d76482fd25ff5e3525a4fffabede3fbed39c734e8d3952f7cf291d637c0a85deffa68953f6169990458ba126b410cac28bcdfa35c3d212f45d6922d423cb016dc11f130fb801ad804397825e6a639cf4348d4cc701bdd0f6e3c14e541d50fec7caa81645277b5b4a0af5b9802c6201c5aeec6b5d41c566ac71e4d8d618c04fdc892fa5046d22441989eca5b0d059543e592879a92cd03bedcf23ebff5a586cc9f109b7575f9b1d04ca1eea3b677966b3df3e631837c8f505d1805e0e2aea1f48b9e8c4443dd3cf2c63c65f8ae24771336470efcdcced073351d36854b2672bf34d03236b504fe365fc8c9f4f83e32d908f6a66d4d733d8779d321c0cccc7701db76ddbb6bf7c64e3c7f853a97482f918300f6360602aedae8f60cd0d6fa6bf69bd134365da63db0f0d7ee90720029a7dacf0f98837cea8bd8ce4d4502e4d0981e2f48d0c11c2222ed6eb4541786acd8825b685b0d87245d0153eaf087dad2cb4d87eada22ef64ead4796d24267b2d70e98afbb02644056a92f2b084396fa42fba78c41aec935d84e669c0999253f972a6c519654167a1f15ef87a79ba6995a8643b344db73ddc3483da7eef8150551857d5b9200e3c7094514f1c41204584289087650842f8e04fda46ff6a3fa9bc55a322cf67d0d51b6e822c50ba814f1454ea4c110472862a20589949c140fe89b3f64cc849b8832c50e04648fa67b5da42794eaed7b386c58753a25ee5714d838d5dfa07128150632ee84c2c1c23f11b150a33958f831ba8c1e7d634741aa33ee57bd61c0f2935202932c33bd6fe64e2581762a095d8a0bbd3b3cc585c61e2c5727b2b7ba1ce49fa594f0e37a1c767df37b3d70601dfca924505aaf2b5e2c6dee2b08ac9938708f15eb6053bda84dab5db770a615f7f0c12b865dba3d585325f272e4d01d7fe783b469b5eb9da46fab2a8236dbd73458bddae3c2e51259d9f8da101b6175a62ca7685d016a5931e29242429696952aad2a61b4aa0cb5aa0805b5aaf808b55a495248685989d2b272a4d5ea4222699ad6ddf6b65477b2e7d7ea3f12e5b13f9c886679602132961fd2680d8c17639431c618ecc088075a96944a313a9abf57a7a4e89581e8aea7184ef52719ecf5260e9636eebe2c9ad4d14911c163fce581a418dceb99cc76e57f5974246dc902eee61411fed16364e6d411f88232b6066eec0e7f1070a66c40af0ea73dc339ec8fc447660f56feb5c39c7f31c11e073af48149ced0626a07d7558022b44870050b22aef892002a80b8b0020a3f504151ac125b3d4c8ed4e08b2eb090c40c5c9093012a745142480a41f822899ccba25205152b6cf73fa216152a4f04a1cfa8944e35653dea1491040040008314000028100a06c442b148241e942545fa14000d8ca448724c96c7a3208651108410818c10020001003020033233228e02189a53f8c2ac593db9b1df8584890e7dadf10bc0c48a43dcf91e9f091ece8590e19bf3617be8cccba6527550910b5064248061ed999fe183c18d0e956a838d74bbded34de032ef0374209cb1b7749de674f703d6297c40ecc6cb0a37f1c88d4e815893d48db9a4817ea6582d975bab7001f968009cb115bde45679ea2ea637326d34eb6ca5127f81fb0bd859608e2b10e193208646dc3274a60f1b1279e5ae5b3fb80f0cbdb28abb42e20fbc17c3c7e082468fec89ae1c2293106e2821d6c6fe84a16a0b5da518d25585934bc69e3bb0da2fe6d0b436c4ee870b6263e620ddd042ecca4a452d377655dff990bc2aedc3c9c6124c8ccc19e7732bf0ba7af63682415f43e147701eb587b1d27da7e5aae565efeaed7b647fe700d242b771bdef87525aca94afc32c3d522a2e091aa7bf192df65b55d9e350a3cb75231e86655724abf8b587ccc9988c5931cc344e4c63746955bf30f98e15480521d958cd4a441171f77554a244301282f8aec3f21682e88ff13b73c501268391b4387d66d9b899ea2fe225aa83bec6b0ffadcdf081ca585ec49c40e2f3076928f198e63e0bb023dee306cec7c2a80074a714ed77d61cfc88a19e12c42077ac5af8fba258f713b4033c53eb99c4b4900dacd21ceed1e3abe29b43263a780cb23c9459c61fbf58a818927b7f1bf13bfe623f7b1d0e8f6863f80944d939fa452b6b8df5dd5cc33215bc10194909ab92974b08a8cb33cf535db635ebe0066b7d500bab3067eb7227e80d4fe558477f51a98211605785439e3d8f4f6453884567b15e44d65d00930831754e28ac272ab493c0eb80e045ea7d4e028c44a84323c9415a2e70193ad25b4b0a26f1ca0aa39a4f5375e4b7cdd0c63dfae11e04a84c2a1b99e8580e3dfdf6a65fbbbcafac183abeffb891bc931ce7c966d58cf1ae4904887d64629b87184ff1278bf8853f199bc9fc7c45f9acaec85dc18feea7d729eb69e01dd5ad34eebe09db3f5d029da4a5d8a5e4d44f850de89ec9ef51b2a4e021d8bab6c33e5d2e214224246d4b6f63a27d15292a6d2a4aadaa5c35c946668ff380b924aaf0336bca4a5ca241448f1925ed40623635031e8c82b98b2c817a58c48279042a1c09a7764481e9905520a6d4ab3a947614c06c7c6b577b5d936f5e09ca291a61cc9d66314ea389a46b754ba63814460da5c40294e500392274ed94df2c7e366cb81bc901ccce82ede76e50772de14655910f35224dba41372d047ea373b70fc17a83ec54d9d2c691304b3ae05206b7ecde8d8f2d4679c97ee27bb1ae247c7d78eddc34da3e680a66a03df338d51ea6d2c4d90f31f78193ec1c5eae308024171b47825208cc1255f9971db200a68be4c784d62dace502141d4d636a6a9d2c545cdcc67d82e335809a48c41a32f75e639cb285ad91d83d67af04e951dec940e39438b35c539e896232533fa03a24f04bcb5367f5f5e52fd074172a1eca784c8693726daa8b0d62f239ea51430a7ab6c732bd78229d57b781314b8bd38b1a9be3711533db3dbc8282a577189836824c5c6127bd9ac16b903c9320c0b7072a51b901dc1d679316c4f5d5b113f8938ece1dbd0e43485cb748fee5c5672ea6dc62a1ac193769efd5bdc58eef760651f1db2a6312154a20e325bda069dca8be5674e1945ca7244da46a40ec33b7783b9a1003a325e55c68c485a1cf6e7a0afba112e8eec74d46def717d81973d144edb30633c841703943a001319858fbb01158a9af89702fa0b0a8d5d06868738a438dddd0cbc0edcbd9fea885cc6bb75c790e5a907f4524643772d5a11ea99764e08636727e55260cead63be8c96e6291c90d93661255ee17a5f2dea0c5194b1672571cc08eb7aaf4e5033304ca6e0afd70522b2b6407d070ca8d09ec1fe3f6d6f69c19c52e4b0779ce85795bebaa84e45300172ddcf226d6ca1878ae19f3c660624486c9ea8b3918408c69663b17ddca18860422b54c65ae8ad4e4520c9cf8913d6dccfa10365458b93a4827873f2389026fc4c82fb42c51352d6317182fc373d1d48bcf052cc69608cba8c37d42760380654f3e86758c93924febb72feb47553f9f4b97f544e69cc0f95be3c2bd25b946f0328bdb8229f1f1a6fbfbeeef777c5f87775ddeeef076f7f7bbbfdbe99dae373a7fa3f3fbce6fa3aefc3eca470059ce8018c15c86f79ddfeefe6ea7773aded0fd9deeefbbbfdff17d1dde7579bbc3dbdddfeffe6e7764ef4f2e77505f8cbb865b01fc6719a9945a304569a2acc03844ba5e24ca85e323dce5a2a8281a03e16e16475df4f808e1ed6241949bef1a7bfc68bb283db0c069d89fcd8362de64240270ec23ecc731ff556016f7f310f46f67300cf132390bbd9db3e97c93af22d623500dceee937b8b490005d47e3937c2c3551fdb69fdc9f3028c5240a461ee8f2d2ca03b2db9e63afe1a696564ebf03ce387f60e105ee4a76567cc866c049996779df7f76cc3ccd1fe4191c62c2d7de801a51ef5e79cf9c894c5ea6d9393b07aca944b0f55c1e6a5b46fe706443341664ab3716dd279122367115f3a11746c4547891281286932a547b8378218373b59271a5954431e2976e73352f3cdeabaf6f6b9227d78a3d078261cc5f27872d29f52a27a9332b205e6c84aabcd55adf4b7acdcb38befae6b951e9d4e647fd6ad84c85e021ca99d7614bb20841963a3b21f2b7c6703149f0632fd78497f05ba91d7861e2fc699caa1044acbe090cad3ae0b0650adc469965470959bf2a5f4361b4897575a360de10cf7cce83ebb06e8579a7af2a135f67a929065e8c4d79896fd09d620d1f0c1c15f0245f11a7db1ac6c4d5046d54a3578310064616a9c678e7b6c92e553bd3fc6989e7a022343640e7b86c71170aac10047dce65558e26b6e158bb303a3b5240fcbc26d9a4c7306218956434f81dbd7d89e4151d3a60cfe40f3eb71e698671ada6b383033be1a5b61df7840ade6b0dc8da2ced8cff4bea0718ea93a9cb118e8c5a84635e18c843b53d14db17a4367f661f6600a7f44e03d41ab32cdff34239c7c6d9a811eacebc250ae16e8e25ed86c87095ed5c2081af34cbf1e72b047ef8105583e994d6214131b2abe3c07cac07d106f422f06443ab0e1a49622c537fcea79dcccfca64d4d9a446a3cb3348bb600facd544615b84dcba960215114c13e8108bde142dfcb1c8654b97e192a440bdf06b938ff39c84861c32d8c911ef0466050e5684d6a587330c998aac394e7b2d6ed866f5a3d7a8853bf88e8144df957f9f067a6ac65d6d22e94665b56b0e06cf3ec90c60d1989297667b2d2ff9aade8db3f4f5d3d479a74aec8f767486040f07d1cb0a74f70ebfbcd2ac68be79d7962a36fc6c4c0a54134b1d8d6a00692323dad575cc7b0d7e92ebe78921f9f8f49027544f3490d6bf30590c975700ee55bdd06a1810b0cea7d23d8381b731ef909dc34cfd1598f544f523b15b72438a4e67a9590cba86d97996a27ae2c6c4f90d35d66efbd08a26b001022177b5524967c32643ebb914c9dbc9bc074d9262f103259128210e73a73694827aa6182721fdd4f530c601438afbd9f27e9bb40112e887c24c4b8664f984c75bb7cadc8107c783f60699d8492d77940bc581e10274947e49b970b817cc6e68e8d9fc314fbe29bf54711ed677bf9565adfb221ba986f387c072ca0d6929e2a758c81deb547e9e8c1bb92df3fbafc87c32d873b632ea7a2583776dcf1a1e30e074500d9bb717dc6c064502c24f9f2475c1e74bdc0f32e6ce6bf0ff1612ec063798624c7fac36c93f4ebfcf76ba36e370652b9653e8c7b9ab780a5a761a031f0d550166d7c7b1eba0b14a2c3ac6907d82b2e548973e36ac955e50bd57875037e99262e9b7a1b7fa4c6597e06f020c42faba9b4ed552cdbc6dac19f9c5d43a774714c9f10f8b6af89e9b00c90f43861aa4d44f6e4a871f3ac0ead09f3f2c7e68329c85107250e6f30836423b5fdff67a2e022ba80e76acc35b1619c0bda9df7efd555bd212d6f13795b2d2f35e15a640578c68a9ca4729060609ee7cc232ebe8739255358ec4962029044a3ad92fa15e1d827f422d76168e4eff77af9a33440f891ce824c1f54a7f5ab7d52d7fd77492319ef5c6345310f5b32b712df21915687cda6c01dfddb21d81026ab2fa8ae0af073d771b64486565e98cc952aee4f3d466480f8ad504bf9d31a8d546586390a5f6dd9c1dac065f472c391bb292c165445b96afff34673f17ec5173e1006a59fd388b653d8ec67660bf2344f19a271e547f042c318284f03035ab72a99e6de1e8268e4b2c8a60f95ea5bc7960abc0d29f9c2d71b8d0445d7c96cb7051e9e388c690a438ab00eeebb2946881e7bb903caee7bc1ac68ede97489260fd82d92c99431d3a754c7a2602b45765a7e10cd1838b39d8d46a3d09fd7f26a81398851be9b75cb8b5c596bebc508e9c700b6d0117d098f18ffc3c7addc238f8212e0bc23511f972495def6e6b133c01511bb16b63798d326abfd3b21dae4ef25a087f294f9f2865034dfa8f10862d77eab2b75d6aa10e3db86fcc8f6a37c99d7b1bbee4dae3dd79f36b1bc7100050be4a4538c8618b1bdcc0891015c48631079afe58fd9bd60e7c2325ca48fa92f0fdc81d2419576ba6f5f4f96a3db5141cb18312be1555ab8dbcc0525fc04acbfa465073c897c5cc39c436f0deead219c9b26178aa814277f2ff4be011a5ea156a1418dfed5431587c4f1122e94b9fe7a129fdde2a185c54fb2abd3764e451256e23ff31d1755dddf0f01c1ee1b66bd7cc3b7209a3a61bba07e78ed6271c6d944d4cbdd887ae9ecf784cdf49122b832023ba785ec726b80170deed650e6c085c265b0826a274b006c3f2d06713559e114a571999d1efc05b5a140eb0db8ed2ffec34147154571a5084a815886aa709598bb5200aebaae55e213d61594e66fe1327274d3fdc6e09026c3d82b58b4f99685d10be7b89d6ceb00d9d28239548433c144e46653ad99a3dee5f181090d4066417fe1ce780458d85b62f2966220ec49ce013a7dadc372585d915ded03a6e8e42e24383f10677bdd3f736fb28d00a13e46c1d3f59a5a7092568e14bcbb66fe05ad0168470db0eba24d70d8774ea59e2d86651e8009c8fc32b5cedecf46a62254ba542c9d95498d4eeec5eb77268636e9df941eeba8c6c44d0e24c5bd69fa41510e7fb3461b50488a9fb1dc29b1852945331c7bbc20308328900a4ef926d25b95b34f8d659713dbcee53f82baa2364bd8def700b3e374b90a9268c1cab8485a2b048bbe7539007fb649b9f29f0a0e3b5d5a9e92aa72e77a27c34fceaa130bbc0d933441b44a21f776179737605ce13e27cca57138d4787c683530d1035679b5cd9a98e742d1379fe937b7f00a5f876297af00424950e1faf6751c33518a5c0c97e3efe938d108eeea4fe88f2a6a710c44a3eb6892231b1b410ed3eee38f7df284560137a0558556ff4111405f2100992106cfea644a12e0d243d37627e1d85d1cb30270c8c7ff3fd11726732973f2488af5b4cc61a7f6a4b31fe3107dd93ac0f644d5492fb4d27d381be12ee6b4f71b45ab3701a0bcc8b6d13fc8c295da734bc71ddedeab3210dc2a08c96480e7d54bcfd75c981e25cc01e2560db6a9e4d396bdba094925a6f72f3957d4b30c6e2a11f3641beb58936e95f670e3d223de52aaa3dc38a7743847a01116acc661eaac2607a3be94aa8beba7ed1269747ed5375b820c74285f88f3aa4404f29486e0e592859b85518a6def0c59da3725dc65403df604f4e1c07859de95503e032cb39ef9254715fd2fd6b4c63a287c24491402d8a55dda05f8f3bef64aea8f35e9016a3aadd0f03a967b0508533dbea7acc5dc12179009566de0a2581390734c66031b62f724925197a070578f0eac4cf998c710dc9721acc1e2bee96478d757f2e5afa03cf9d54c507b994a6237d3dcaee3c92298ffe594ee2f00819c335a8a49708e5dea6fe6dd43c164219f03e3e47f70eaf5211651427a8003c80ae14899c8dbdf7487ac2787604813a3c78167f60348d2305463f7b594594ea8eb5c142530058837ea17613a725256031fffafc9e87bad6668e1fcf4623d167a67929c15d1f8df27bfbe5527b622362f08b08f392b36d1684a027112ecd76154f382b569dfe7b99ac5fbc2e2c366d0069bf7a84bb3c39e650919a1ba3080f3d9346dff27948808c97505294fbc4e117792907ce38cae92efba4ae8910234c893b7484be83ad34f111736e82a2517834bc8ddf87694b6eb28d49e208430c9f3669b0868d5469a05e5d3d287d35ef0656ebe03c26be46ce04528a201247786cb0927a8817b514f3335fb089ed01c0106dd19a1c0c227d6c62c660fa777a0a4ac574de82f66fb04185d326529801908870fda1f78e33bcb4827bd54c8145799633eee7b8c4027996465a47d5d383e797551add939b7627b9e84edb4601d5b29d580e97bb18a7b655bfa205914084c22139b1317c7e43f2c6b9443423d92b65b23eef6653dd01974a4f9545bce89cccbe7c9727638af48499c7db34000a90a0bb591ebc69ef03796b1284d83f3362e9ca7c2b579b9b1cd13b2355d8c7ceccbce5b674753ca2e65e6a0e83a6e5978a5d9efd5a12f4d37fb12312e2176dc607e853418cd7be85a98c857c62013e3e1ad2175e2ea985b3cf47befa2efc57bfdf5139478c45de0b44f525f4d0eb2611aeb1d314b5a95c923726a00e0c4e36217b0c458701abd5e49b7840f7f5593873a91e6a4e02dc5c063d0c4371c7ca821fca3970235600694c10fc0e7010f1d361bd06cc8a14f0a0b441d64813bf31ee379aeaa5bde0c16dea9f6bdf74ef361e72004e0a7846d94be2d3bc5faf6644d1275b135ed0d75bb231d7c09aa922b0f01719d5e9f1635b4a76d4c34f02855a027f6fe5f59bd10891bb1d82d0c69e70a08ed75da062ca4da096392c1de8017187de1409605bd7c50e58120078a6c90c806291f6499a0c901420ee872409607bd6c042acb44f90a20ab65554d992cbed21a246398a7c434c5a629364b91090a66506e8262738acd53d21c05261498a6c434539098407d0e0e1929518a6b5d1b79361dfa0eb7d266e102287a329711f163d5299748d9102f95b5985a772517e382678696c1a590e9cd7de57a73319ec0059de1613da252d1b720abf24ccf0b52063f2e74969ab4d6d9f940d1437507082fcfffdbee424610a8c7d5f415cc5061b683c3b1285531a0105669acdb8b29870517a05ba801458162684caeb089fe222b478cc35b2ee6567f566e1fbee09a72ef4d3eb86315fe821bcb74924caf7f372c01d6a7675306799ff9b4761647b0f34a9354c68b14e32e619a0b1b2da496af8a1f80ad22b9bf3b84f8d6843601f0ebd909370040afc4abc6b12826d0ca1d0b6b416e7e2bd4fefd72a1685cf622baa15702a3f89248d5b1758eeb0ec4c5580ff6008b6996bd2a3bd7f62313f6edd930ef2c594fe8068672d4d94d8cdc21277420ec2f9d713a74137e72adb3ac345559752e39a495f4741e228008b7e8ae620b3b8c9d91b229a46b206a0075c7b45be246054fe0491123e9e1722ef0865ec144fc19b224511b101be96953024ad257a29d2f3cef771161180b86acc33f7021553e486ccd9169721a1cc82373abe2ac160241e8d006222b9fe4a64702f7cd2a6c26fe7615b204523f74a9e14921f62942ce6411f192311fcb0a19ec02ae074474757cbee4d9037ec246f8901afb8bea60b8b1fb28b10290225cc98c6fe931b988417f303e3ec0097d5ff6a13d97356ae556ddf12410d88a8ce885b1ee8a1c305735ba00fa7a4660548182262c54809c4e2f3a46f637a6176584494aafc668ad190f5c0c276f9574ca354803d85c7061a8ac1e97f9da3bf3f27636e7eef21e327f3de122450d059b57b63865b48b841b8769cfc6cf2e9e23abb744b40de8830b5c4b6465776892d0d7be43922158429fe7a23b013d00d9471759d5aa94aa25c8c5893bc755717e3be4c5aae5b97e067afc626c5474e82c7df721efe575046b6152908beb578dc3a0d847e8f35acc2a742c7bcae9596da0d12be5c4eb5b5cd314a50c34b2f6be69845255b3c95afd0e676f83f74d853d02688cfbe087b34fdc6557f7810daf0ca9e2b5bab7be04415dda1fceb64cecf0e275ad6b8ee043878a0792e0e9d39b06facda256b994bc953bc7724f65c1ee982b857b5ffef7105a9ae2b7f9fbcd1fba59ce38593eca32ab26a2a91d290b6d9799f72de198a7c8a9261970bfbb6c68e6b82b2da781626d5d09a5a43476211b3c9d99cc127c0721de8c500e865371efebfa30b566819f41fd1d0a5406254ca8b684b399663f9f70e419a4eb5218289887435221a65c204d8d324288d883becbe1f79fc0f4d763dd433909dbb4a4d6058deea0de853cbcd15af63ca4f9e2c80c960621d2c745250a42bcd3d509bb2ea030a84321292b1795ebdfb803549992cd3994cb05c7dcbf1172c3387373c202bf12241bf53bff4669fc1f091ae1fffe3f5ddec490f48fe642fa14d739d2e6b291d508702cfd7b4a322ffbf51cf2231cbbf5fe0e5ac86f7a9b47f2edc1fe84331fd872248891f55211387ffd473e9cf9c0549b13046f79329e9669f6800ccd40201337f4b20e13fb37fcd8df1102b014271e530752499e6c0a61594e3b07f2f3a173bff5f908eba3e30b811c31d27c6c700cc9f9579880266bb08bb844743b0f4893b72f170ef576c1868929b119fc864b69ca33f8862424382c76cde7a3662fce19a48ac6224f37fca6fc0451cc0018f7180b766e51105d5d3a9f22b34c22f71d0641c7684045f6c9e24941d07cce23f0141070b2a28ead42148f37d58a83e8d7e22070a984b557a30003e432d82e037cda8acb92aa51eaa0b32030890426e308c81459648839100df4b22101b5fcc827c44781e66cad539a5c0b0dd4ba58726235823e388bf5a2bc2d5126228abbecb06677c7cce52ebf737fc48a0726449b5d3f37fd8ea24ea591dee2efa2ae8f860766cfeacbc7704c7f6f02835eaaaf3ef93726f538fae7f511f50298554a9bf0ae3a93c9d76914a94d9e500d02b2c86b362720470b20038d733e7e8d21475b7de7add09829b86448f6e30d61a3e0c1e838b18c1756df8daa8b27a8a4f0e9e19e79aa74217545e0ebf7aad221da57b04e95f3f210cb45a6464002997d0c54681a3e8df2c3905956a3d69a067725b6f35cba3476aa7892bcdee8aec6e18fa9a61d1b9ece698bd9789aca3937abfb270ba89426f0ada8dd210e81028ca3066bc5f3da9a624942552a5e956c4406882e5cc6950176cb3eb710d1436afcbcf82acb5c2961e9a75dc9c670f47fecf37c7e892a9bfa8f80e14d5f3744180f5359cb30eaa97fc0d5a35053ef0fa46b37aad67bbe6a73a5435e3bcaaaf1a94269bea4d8ca57d857f96a2221785e052f00081eec02c284413385fb4e52e2830e8e7315535b2693b59020238e6bd670daa60fba165c411f594b7d98dda9827edc000f91eb016c173ca1657808ae5ce88ce5f4feff1399cb390d16c63bb5c2aadf83a8f8374aa0b32249b4048feaab9ab1e9886d1dbb86e66880a83d6a3bddfc19f0da64a9169f93cf3cd0b04a29eafafe18720058dac691d2cda354be39b195bca845e2703aea915c86dff74e3fdbba071a140fc3f1f58aa5843f808f3a7bf04bbc38f088c8f4003cba67433ff007e8378b0d6756fa9b9411023c56fae1fa67df50ac7c948be608ea279cbb502be7ae3266b19727f20df9a80d420c82b727d413fe4d90053aaddba28b31ad661141392b80c9ca5207e9e9a85e551e6c9f11c943a1587da861ab2d15c158e32725032d486bf18262f25fad58120e9dc5b12200850bc7374c843e4be875d82b7bc7a4194146f7b3da74cbd95c6a5c5ad0224366d36189b71c0cc60ff3a4b6a4cff802c8427b51f4edd88054eb8ebed0481aaf7d34e9c19ba84c48f63472370f0e979799fdf600b526c383feee9b95d5568b1b60a922691064244d4b43020cb5077f03f91f777ba7a3ea544f9a1a932b6a46b546d2d09225144d4156de2b76647dda23460fcefda98feefe1a9a777b0e4ef11c5c0e6c42f852d89e21f6279eb4d84339e4b50f02c67c1013a79ca183b229a93ffd7bb61244fb10d1e224bc905ef447caf1eeaef572a831301b43263c63d5a4292dc28e62afdbfb40ae89a4e900d0e82bdcbf3ec55c1f56559093f68193d5556f224148cfc6736be45f860dfb0e68f4948082c63804a22bd5ecb7d4ec60c18b770c70a2e41baa32b01753837e2246c394b80b66c82dd9188b566b4f40c850947eefb6d970bd7ac9b947cb0972cbed362fbd87e4aea6d11e341c8407b9f9e011ffa72d1043f07264f65023ccdbcfb2bab6b89d58055688bef1e841a0300c9c7d4b4b2c6ece7b598a856918dafb1de2f63881b2535065aba79b347117677490729dceb24bd5a403341fb322bd3bc8bd98e3b97efbeaef16218c71557a5a1c0f048e314cb92c6ed6a2e23498dfde92e1f28500d75660210766f9bb080fc7488f7e03bd2ac9032f50c9e7b18045cdf6a8f4e5d7843950f396694e37e25a2dd625930382b1dcbfb8ebcd4fa295466bfcbaaa28c4f1cb3a5b00b6e1df0c837626514e74ef4f811c6f0917e417a609c41a47e0de75237c84f2edfe70e0debbd7872b2e1e84f4cafb7b95d2cb9b157bf983490acd3a16697973dfd494204335cd60c77b4b6d56ad377ff4d1178fd1965a0f5cd92807ef679b56a32f7316a803be746fb954bda619f1b45aa61ee4d9e9524ac6f8cff1d68d9625975921fe84ebc6a57ffb68646e1628b8b28bc77b777d27a4e25bee94e286dd003adec8dfdab7b8076fc4fa141e387359a9b02879cd32584a4e98cadf00e35a7868bf5306e4e66c930b38b2bd2e6c786c8c14777e0cd83209db55628823700d62a9dc1ff53271df42f972ee5b8e6f95ded02330bf566348e64fa7124e00e9765a582e0fa63f82b6dabf017ecf5c2bc37dd8ad74e9039c37c6ab8c4e8e8988075ffdb8868fcac2d26231edf858ae420f383a449002a55d222f09cd78ac57f547d2878361cd6b44e2af0b1fd1622123c0dc042cbd7c67c8a5af23b17a06f3c332f830203fd26ab086979b80ea59b6d2a18ed8c242260b82a6376644dcbcd4932534e5cd51b9b253847d0bbc8762a00d1804e01a82fbb73a5176bbbc8e4210ce1ab6de53da8560926492c3cdfa637da9485513dc94b829dc944fb6ea13156981a7cb3972fc13f6766ceaf1f21ef066d4677e8d5a2f7c81ad0408e3961d6a0ae567987c568dce62edf5796d649e391cd48024685e1ea9679abd9ca042f72ba1d0581a5ec1227b1642f862f60d4dfb744a8870ccd268e2b5728dfadb366993d7ee4626e5a71101e6f8dae63abd0139eb4842b9c05f45e27ba037b2006e29a0845f279b18ab94a191cfc5594fcd82bee7405611748f800b396909e9481517ceb43d7e2fa734ef120e8a5244dc9e76c92d002521156158c56ee9f23588250ee8db22205e7c724d9b5c403dc92bfb1660f48e483a13ef4a299ae747e5c82f6733bdbcb9b65d6c0faaf1aeddf385412cfc23c61fcb54803958423df1fd5e0c3ad88c22731792a988f764516c94e7570709ff55024065ec8a6b97224387bf6344dd5dcaf0bcc9ff575788765d31b6cc4664d23b68eaa91fffe5078954c45b7c5ed7a734c58f63a529b2e45463dfbd68850c3f5210ecb1339bc549c108e3905813037f94fdf63db435b4af5e50429c77a4f28183a136d477b3b06f731038b563b3b952cb074f68e3cdabf9362887954292ed3af40038547bdd791b3d62663f02159a4fd5775dcc584203a00ea6b4f2807735c12ea9061d4dabc93586e8c3d5386cea9037bbaa450b5100c860f482409e38295c6da43d7039086a22287435e5e519544d30d31af4c20256fac222f4d251c06fbc23a47d7e500cf15f9a4ac361eceb2fec68a8d0084a210e1fe161e7ef6cd3b856d39e3a119335ccfeac210f0a3933d2e8957b5ed896c3c6dbbc1bd3cc49d4fcc9748c50a238d15084f4137f1db4ffb71f33750994f5232613301250514d9dc1f7dfcd09b0fa31d949dec092b424a680a33cfbcc392ee467ec0ee7aac44357503400ea0c69253dbfa4928bbd8185ac747226a55efc21a40fd9da5e594fde0adc6fa19ae13c2b6dfc403eb2e00d0e3de7cf2611a9db43dbc3ec73663435033acf143c517e44277659f72777a987219144ec8591f2263106a58503aa8b64300005f62ea92e11129062b0b94315c09ee3b0623ed80346b3434a4e23790a8224260116fb5bb78dbd5bf63c04aa7f7f49f755da68cab4ebac5e82901922e3cc5cc9b3537054621f802621d5bb8793286fd337c56bd261875a45727171d77436941c00f705339666983750bb5e1a812274c15fecfa94d2fd409ad440d7c75997db3c06504a17a4ce0a32a780ad5487b302e7d498839bf57f41bdf153a20093271fb669f9c1a8eaaed2a7e187e75b7d238818c7d54530d9902806e6db4e272c9a481e0a1c854e73eb526c8d06fe4990bf13998b8891e4ce30ca50c3b9d090ef70e955f1fd73ed82ab053a9981e4c89d3cad96f77269d471cd19e0a641269e32b75c1fb1959844f7c8fca512eb5d927b9019b362bbdb633059c044ca77e7d3d37cd2b24e5d46c3034f52cbf863afb9749178c0019d3b53ee9a5a04b63bc1c17dbb3e6bbfe85684b05a32e18106ab06d7db8759447abb6b334fb742f95aeda71cac957479c126dd9df6694d60fb8f546ede90344b272229eac01e122775913d643960423bc49ae4818f1297a32b8bfed60395c9683519d01a08262abc6c163d33dff6515bd371659278cdc557c22376a6ae180931771d0c597f1ee10ea5138d9d9f7b4fc282ad2d5d04ba64f1c93dcd5ea77fa48497a7c23c650a107da469afc1f25af48b169ee7a4d798f6397c3d8bcbfc9a00f54fab1e5a6466102887bfe2c08443731dd4a929be9c7b108c58560804174c8d444aa9345400110fafcb033ff5a01444f94568a85e2ec0d9c39f1dc9d88d93c5768e6c00a16ec4ffb19ea2303f41f7d8a30dd1617ff35c2df55b366fb3d97dcb6a2746fa275483fb1ff1470ca0721e273e75c684c350ea11118a8da0059889350238a5d5b5cc7178f4b9d3a33233859302885d899658adbc20a758e2373994a1eae538995ed160b029e9ac3bb9d0dd62813bfabce3e3a45ac0b3372bdc707ca6f532f928c3d623c24fb820865df970e58fc017585688055e0c0acc370d71a6bb132d2e24b4631bde3830b83563a7ff72fa648d935db566e092a54641d8292c6c364dd5a531b494a021f62a8a59d02234159389b523f0e2b9d7530d84e7597c5a4cb6da2a307f2bdbefbec2aca580e53dab43e6972dc802e176705069b7f836f263e42c332628f0167a08c38cbbd4d8cb6368d6884a00391375e9f3004dfd6f0d1bb385dac45a4652cc56f8340affea585127596bce92650361d2fee254859990dc6f0a9f8138f007aaa71cb451f236a3480f153a73b05402876c11cf4dfa231a6e717307c46d28626692dad2b3e6e0d90fd2dc3f249e0324105754f0013bbd14003b7a917334f0c9f54e0f0b54ff24735fc226848143bbe6f835b2cf6b4445c54547dce73e6eae9e9beb00bbbbf244642ccfc69b47523a42dda5913f1bed1aa7c5040de4fce431770c7da2d32dec54e116eb10a0e7a3d6f8a53aca3827e3569695f17fd4e712ffc7d19095c09d580263dc098414ed995b0982e39a5f25b087f8c1bfd64ad057beb6a87207400c98e3adb98f4a5cff9309b149d098fbb01fc44b72ddaed42f886aa9b2977d413b2f20588e4bf109002b72ed6d428f1ee97cabe388059c77d13d7fad529201d4bf8e7707797f01d640e557a5ad44bfa53c983b6dbace0e93a5bf43668712816b052e864a9945ecacc40ffe0bfb686902a0a5b59b1f960e5a5be30acd52f685a0bc1b840cfa9f60ad9f9c3806080b2ded718b35a3ad3098aaf07916ae5e55651ed92d3f36b23bc6d9f464e569d81d7abe5d23cb5bd324dde9ba19105372f10dce6964d1f9d97e9413d65889ea9e8872a858454ed0a26ed7eb0d7d211285dc02109d3d79f901389c105b1ccfa33d9f3a789f2bb575fc8cb5dc184a0953d6dd6c458d1d5440dd0e89c1ca72ef4ba89083aafcbf7e8e9b3d5f27025cb85110cb0d2ab3f17bbebc1b1b9cc49cb957bed2ec0b159a8728d4c6531cd324c12fe8e82883ad96a1e3d27b49b6717c2cbb5d4708c383f2dba74d436818f48cb5f3840e0172d7f781bf45a240b8cb6e91be0e120bd5019ebb700baac0e8bec9d803e666f9e50e45ab40be2df2e4b2547279870a1b0ef4487aba7d4015a25d70c8f8dd1961d1ae3190842352cd824b0a206848ffaf15d83abdad2a473e64aefcf654ddec907909b014650d4d5e95b937cf882320c63551ef432e52a0710947f381dd5656a1c4eca39cbb7d4c08833eec3cec5664594563df6e274123b85f3c55033873089fdb3a478e5e9779b0dc9848d5259bff77be992dc562bea3257c8f755e766c196520f9a987b8fc955ffef9f2ad9810baefbd6c7b112172b040822d46d95be376487afe4714a8423be1234882d64a93466095b9d5c2ce7ec54f8952491734212b7dd8f0a8fc7377a051c1bb643d9388e8fcb887b1d2a8a0097b5ee30409687495e90301d0c2e4f092ab7ff36f48b7c53442fc95edbb16456b6b0251a85365ff10da25caea4015594c9e8b7cd59330e223dac82dc6a8658ecf1b6a88055761d89f166f44c8375dc7f0c635c21f67fde1c0b8941263ab3f2a2d79f14af83a666697d677f11f83b7a084eb1e46746c6a3235c7493070d5205daf00500ed073a41143694dc0fc0b269b60c783f7006ff478e171b10d9e8e7004090198423829a4d9465499056b6065f710008ec3630ca6400716a79bf53120df8ed9ef7414ad155070d8573990e34b0528d3afca3d3ccf5a8e767e22c4b4df4fdde11ef9c2652fc4a21dfb70df10e27633149475a1a1fd4e84303212438ff4cc8dbb62cfe8207c7fded656f07478a96447f22fbdd723b8f6b081f620ba8357f5764f58e641fa310f621879f5c58c64b3f5f1bd385c45f0195c40685f49dcd16a2138d611ec0d45a15feaf2865dc7c1bde1cb2b76c9555147b5956b035326d75ef5b349fa2c26569963970c5d2c57c8beb07cf2ee55b5e74a88ed1934d044aa17d9f98118e3f5366d3239a98ae16475dae6bed269d5262f41adfee64e0e401ca276e0d413cac1214b4efe402d5565c81f11217a716366eeccfea381069f564064c0d1e7307c7b9201d426c361edf1a6301c9c73c13419d1d4c74d23eb813dfede6d0b747b33c29996c7622580cbe07be9ac54bc58ecd762c9dbcbbfe61a3923ebcf2a8891eb228ee86124966b302d28f00ae0a20aea56ada3fa223a484c1ddc47d9af2704366e151b3a73b882c9b61d6679682bb80560072065f00323a83c34861a20dfc0078d80ff23b80bd08cf92acb45307dd93e2a272ecd0dd26e81d52d1097c21d7c35e3c8d28bfa730780f2011c6c619830b860670c37fc99bf91e5cf117e4adedad07ac59cfa1b4c577a38ef1c572ed2f2f8b19ee5ae9bb5db0e4509ccdecbe0638cac7fda4a6bc0d1b76c7653e1068cb6cabe5c14c3d3e3b3f5a0c71f1affd23c2cd22fcbd8604c2e48aea52b31c87dbf6d01afb88032293aed3d64d569d5026a53a22036cf0e74d8e050ace61695ddb14c96dbb682b58ba620aa951a961202b6f2ff1501fcef8cb403457cff719fa538e2bde80148b8fb56c46813fff9c402a61566f0cc65f146d626c8c4c2159fe3400624b36be5450eb26793602aa43728f568db518d1a44740948d7fbeaf89d2f83e1657aed76110a7f580f9ec5c38f4fdc37aefdd57fd61237e7cf16b315e8833dae142d1c3aa173ee52ae7b5f12513f8a8f4fc2b43384a624c2fd1e84ecc8569d147439244fa7d03b9feb3cfae01fa70cbcbbd30614a47b437ef4e95a73a2f84424a0c71e04b57d742a093c4c259a48c02f89e915e8063893fb5f72ffa41518b6f8dad03490ac90e468c66eeee2a9d54dbc871c3e090ca271ea3712fda73954a210e496f138552d589df4776a3724ce21ebc0f68d0b06fc01a4432c9a9c2a32ab436e93c1408268df80a9f2e0f569f9bb9186859d8294dfc381802b048a9457ab57417f048bb3d02c0428c649c6b1f92e4422d219b63fb07aa695467c4d46b689202fb9703d75a051cb23b82e5c7452cd8de86b8c8f9e1b48ca2e8951acc5e754c26a096d2cf83d3493281a3c367dfc8aa3d860f005cb0a7083d338f1a31f2451f82903e4d9336b4bc39e9e82bff7e94442dbc87cf5b03a47fb429b95d05e60bcd923eaa5ff97e2cc4c4b12ccbfab3d7e19109ac3634a57b4cc924eecddf5b8fac304fd28422fd0a41eaa779926ea89bac8062876e2864d6e011708c3794851a22dc65fc99c25da713bd0027a54ec26711bb20b5993ba79979f959affe4c0e8f433023b267d36870dbfb91b9c41ad528b3b19368a1a36ecca257a8dadc6c93e72781d318e0fca097bfe4aaef5ad8fdc5f20363b9ec2de4c480006e80f2014b791f644b890fc3ea1000942edd46974cc878d6f5ec5000cbc18408834b393c52f0ad3d4bb667f9b1f155957c2c9a8d2169a902b78d64c7185e58748e62563421e1a9ffef08cb36d1e63342e5f4768c675e07d248311f17e8972f2e3685220c1842f21be2a99c7ea2d78bcf1dc7112ee20c11875dfa53a763c017af0cc25039f6f26804a7bc4ee5624034b8ea3f4c8d3d798941c7c92127852abe6a1045fc4ab1b617236b1d3869db9ba09ffcafe5829fcff5d8f8e85ff6a8d657c5b519ef7206ef83f5c11fff2bfb06ca9b95964fcb1425fc79ad83540c2b4cbe072e9e8ff0a96ce6dc295e0a5763908045e9d0203c8365c72b1763932bb3ac0cb07109ac6bc0b479d595d69033f3493f8f82be7d6400d21d3077b3325dc2170bfd98afe02dd8f68a15cf859dea4e39e8f7e20666867408f6accae4c8c5e5cf22acfc7748e80c4360a59ee1cbe7e9740f46e50af1766e1366a4a72988fe787ab01b604a91e3363c770a809bdf11fde0e8a24bfc7f05fad1f4a9ab8bf81cd4e4c02f17bdf690184cfcabdd672bc37e0f2972e3278d16717607e0a1e93ee9ed846338c2305c1b31fa64cffba0d07e76e5b123f2f3aee150370602b62a4b489f36adfc9b5de312634042a6644c50f5996e3cc34e6cfdd56560164d73659e833b865c8706bae83365ea3563de1fba2d055847cd0f0af95b1f35e6631ae0bec7cc50e2ca126e3bbed304275cc176c53148eac625974e78b0a058685315565da4e8193d00cd80ee0d8091fa5e6553d4e39b932ab70d90cfef768f9fe2e5ad3bccd2f21d3301662382eae932ebb407b1980bac067cca00d4d342e97da82addda7754be7ddee6a41c43600545197c0aaeae924af74b98f652c36504e8174ebd7e82f02a0685e5cb3499b6eea696dcee2ccd671bf7e86c5db18f6f00c4def546c8ed2174f740aafae5c3d118419db9edf0fb6b2a32c95c17d64144c1b68291055ca109aa156aad4f3d9858e19b4b1c7baa706fa8bd87eea38db97f784dd4ada2cc0fc868ed0f60e66b3c3a78863dc08927c6eabb5ae77f240b371593d6cf3b107fd4dd6b679a3a5d78283411b91b9051822027aaaf03ed7db4018203d4d41d617d36b08cdfde4491dd2f041a2adf2d0b7de8edf1a352938045853a40e2c95d0bfa01a7a32c8d0a93b30094597b00857a23f2297acdfe5feb4769353b6654e4a88a333c0b26abdbed19c9901d0ae6c03eff12ab740b135a013507e6f3ed99034ea0bf062c6a78ce2919f82554bc77d1bdb28163127e81f0bc2a4dbe9432d0815c1678be989ac0168763ccb94265c7c12853f9719dadacd87e73470a439dfffeed502a8b8cd2657a71a5e1ddedf34bb55b98a1a9a2ae387614399a5d32b4b18966ee0804fab4a70ce6fcd10ddf57d86020b7a8643af0926ab727a0be8971a8c8240eb1aba4d25258e7b4b94ac1ec4a16938a18a772375f49c2e7f5059d56be417bb49d12af3f8d422b9c370447c266de42feacaaf62e0560fc3cd329b6385852f50a009459ae86c7baaabc5dbc6f360d8e24c2e81082f4f29f4d6db956ee9ecbcad14556e957ae703be5387fa72606e493032088a4af99fc51fe467683dbc93f4d7a0f011f30f7ade9e022ec0eec10856172f27e417ae64fbc68600e5a0fdfb353b34b54d3d2d3b13dc423155c419615b2ee1fefa7bf172e72ca26bdd4598f9986ff725da41505b77d85ec93679ea0d0a8bff0fad572116e80a2072d83633b0e033f63dfba5d2872e17f98bc1742cabc65f3e7adecef4b356ca894907abe73d0b773ec5ce2db7f0069d960b97141a111708842178316d89ee616b3e7b28071b00baf7f23d2a0c128d4d5d26494600afcd7949b9cc4b0e36f7bed117fa2debbd5b9d1fb0b1d2fc2d9dc5507beb06d6f5e02bfb43d2731648d1775bffb793e9a983e33f77b40d85745f4fbca71d13c5ea62949f9a8617a1f61a53201333d7d310c90ae1ff6cdfc598f8c22b11f0f5d7eae49eb59673241c55c9c29ddf5aa0b7bfc1b8cbd9e2569a8b505acee2fcb52b9e164cecbac4620648819d91f62ae7ae5d809fedc654800bb6ca3338f127179ed85a6c1006d434672bd986e593c2d06ceea22d36d2bfe210b2684384c2d3e347bf31991b30e0de3a3e68358ac86614733a3ddc74d4faddac75f7caa694abf5605986356ac3fc6a6ab58591c90d294e94c48be1e45d31f16ef3e908d67e1da8c4af976ef3e5d091a1eb9debc6ff9089ad7a9ba6e50753a2595e410c802302fa87f81e9bf31376a622faa27e456c5940017c09c94a1e40d4ed2e2b88911281f39d1a2adb89e2354262665fb15c5d17cc2397a5a3b7a362e29f864dc0fcdc4ba59211470a1787040c907477c29e62e7f4417c27e25e0d8744856473dfc9c8a0b4f4c2ea47118fdd3defa2eb319ec189ad0ab460c02bc30fedddea0d590eebed33e36e06e6b0fe6e3825211352e24b8a991e08f106f8698d2f29de323f86e53c57a1ff5fab7c3dd460316e249ee104e6fcf7b2ebb417fc7642994d5a859d3e17b56c8bb817c434c5a467bb16c8c5f0fd3b794dadbc8967d00f568b53e43c75beb283fc9baf96ea9c081c34e2548a6244b64479060f300a50bdbab89e1a1ee63be723642924e4d20ab460538f26f10560f9ea34a65309cab5e8fadcc49a94626b4b47d89b1b64f82d0c272d78d7dc44b36aa11cce4fc527e84f015a0c1d9d04de20d0523aa3b7aaa60deae3283f9b65cf5adaf79bd17bab1ee901fdfc20d45223ac5cc1487411fee0178bdb022c8e14b9b1185007470814b306f42de89f292b00a34709e2dc81a52ef4acc503c7f2e99baa33c9fc441071b848acd842f769fa38a77473df5877316fd31ea6a4b805e2c7aad033000a5c9b48126c692a8d2856855e6af6330c71acfc835bb389e03908a32954afe11dc65f8a79e724c8a578f9db46b4ce99389e80d9b0041a141ce590e5007d96346207fd8bd9ee435eb566bc4ad8dcbb5b0fb3a54f354696770a7e4e6ccbcf90a3090cb6022ab1155b204865763077924070783f8120526267d8fb6d0b6feda500974850f2448c9c1a0504e137703b096d4afad6d816f5b2650f8afa257ec37a1d10e8abd00b67b935b7adbf0436b1d5c186ab2897e93e1631652a21e094e33b1d5a0cae6e70e1014d43905d8d0ca68ca49ba37965ed38c9eb7211f6939c1b2989095a4c9a70fb9b4ac7f054eb00f13a6f0232edf444a45281bd5c35a0f288c3e372499bfadac112d1ce096f7afc7a3d68f918f41b6ee595a7853358a14dc70cdaf450c24f0c42e6c8fed448f6c7338bdabe983e43a4d45a2ef1dff0478c69c53b64987eb1ed47c657ac0a756f5999725d0f302187a2d9fdfb258389afda79f749f6dae38a8338aa7574e38947a58747b5b83b2844f377c22280425394406ffb99bce1923f35d6125de9630fa4eaa9222a0c42d028479c74b73d067a00e698724532b0523af1adb36254cc987929029ae1df18e496fd831a30b3174cd97529132ea09cd6d19009fffc68eb68ac7603a9a933702f58cbbba0c1d61c3ffc5fcbb4e6ec00cd7b8ed1d2cbc9e810c04688e9e688f2a7b442e518c42859be6f16c02c40a5478da86fdfb0f4af59e704fd50586b5126ad9c7f022e6e120a25ce3deb5f50c15064bb9a3e6dde07a41368d57b583c4345d6d2199e79ca9f7cb9a49091a08a3160623c6c41f050e54b16be21ce916aadb28819bb648a4538c60f68b5a516d09a6de4ebae0909f97f8c259f3b227ba43436de6643b12ab5827435b9acbd2c398c59d3f818e92e87cb1a8f354b8f48a81cb34db91810ebb0c69800c0713ff91b1562fe89c2bb0f937f5cb348b55b4d011a5c75960c49de2b79c75e315ce8a868d10cfb7f6d61c4052d9c32dca69dba9d0d949b8897d465b9e7c3a483629491d7f2f4762e812ca8e5195787822e7e887a8bfbdeb8c0d06aedf19181162fb9f59eab9ca9aca26604cee5ac24b58d7558639900c811ba65357fd3411b72b35818d556ba8729c388f87e79190a0baa0b352ac019d512a129a4a5092423e457aa0a2e63ff3231ce4fa056dcc48bbf42d9b345aa14d217f35339d02658db3a925f665b2de9812305db837b0293e01027a9b40e8856d43605edd7ed917c4c17b561645d2498fbcdaa376f0a56fd4bfceeeb9f8258cc48a7a93fa981db32716dc62408e483d42c3947c6053bd758e76f533a694a89a60087fb1454904f215fea009efd0b9e7354e86f8d1771dc4a0ca4837738ebd932517073625a5e5a13bfed1b7f54e346e5defbe586ef1c85724e0abbb92b101573708f4268f2a202a1c70bd40a8a9b779d7a68ba67812b5a04f579b59a05f27cc99dd3ac97de6260c335a2f87fef3d16bcac1c0164ee993b3fde93ea93036494bf72eda48b661778b73e0eded587c8c7436522f7ce336aebce41d95d0f9f439adb776367c8bf119ac4113627558022aff8b00d1d4724fad3983895a77c82fe09e7b1fa441e37a83d2bf443204de76a06c4318028e4c0016cf30637c8cb531d45ecb906d33d0451552b6c2c0d8d86a62563d833ca63eb3c70dccf6a1f9acbc6c4b0e85ef3fac83bb3acdd3648570222ce583cb403a2dee151df0b439a5eef33cfebc9320e2aab002a225b100712f5ceb0f59d2e4989534fdbd1b987e6c01f707b964682291eac5b513887e8944477e38169fa213cbe5da816b57f20ecead940c1fb8066522986ad3600466635e26fb2e482e4ed5b8b113993aa62c370b3d36050bdeff6c852964b0f7511cde0a09163559e432331f377962a3da830bdd855a4c3f8a84d896a2d0351a547bdb1a3ef39cef09b940f68c438bc45468d9e0cacd55df2e536f38868fd36f8e913e4e2220495fa2d284553442357b07b3fea6115b42666746091f8ad55f050f7c9233bda10e9f1972247499aaad4d9515c919964d1846d8f915d3e67209d1bca575504b150503d465e29a08aeebb2cb16dffac56257ea03d1a8c2b99567ce23ead111263a94c687ee2f64d27b3f807a0eb32277b96ab611f537702ac349b04c412c5ba899a896a40a9275b6943ab633ef975fc1296811c22a1c1c12e9d5922563d199e5c1f08988db2fe46f940f45364f2448c30992f17abeb7308022e1d6ca59c58e2965377e5f7368c2862d4360bb5bfb688cfb2cff05839427ebc094a5a041ee0effaf9746c05c123069d6aac6cafcea28ae8d341a27083ded1125180c97c185813b57fae221a82403cdee0e15112aac5b394c5e6fae3cef129bae28ab171d334ad34aa4dae4d7101bf678bd4a48c97bbd8b7b1fd6572aa540c58047ce7ee393de140584912bfb5e2c3cc6a51421571f1e4dc5bfcdd529ce5deab631c5e07b7cdb1ae1ad80b384ccda784fb308a7eaa018858d7c992368f8062d4cbdc38cce32a53a8f086208794783dd5a6710d77119821c34e83eec50dc633721c993858fe5f9b4d5f6a592f098f26f4fb1998f6ec47ec9423de1bd810ca6a401dbf533866ac45b85509bf692c44175c2d3fd87b5581980d73a70d801abf99c63d16fa57b4c0593a4dcf794c3d1d8552df85bcdee98d120237f15267798e2469a5718408634f75416ff44105c1e20c7b2558587c6b402c441a80f1027332401346172a57c0c1055c9e355158731432476cba508949f5926461bc99cbe9670dae82ed82dc92dfffc3fc2e61786789b11178f3c5dd562629afba80e84f3c55a2c7c972e21452bc30ed754c9b41bc66808d72661278705e7026bd1baedb9338e268355994333f7b6d90cfda7b8ff0451e573f897bbe5cf47aaf921233f9654dbe599986a0f6bb1a0fc7837179a6776e400949be71403bdfc554f00fbc28020dd9f7c033279993d119924caceaa6c5f71a94a82701b334d82d7cad6f44fd4d288752ad508a53eebf2e0652f8ce6b625bb78421bc92dd5fa2333b7a1137c61295632c451ec93acf8a7f278ab91fc2af1ec16a76555ab653268ecc654d46ff90a413196c1adea1fec7f66b5a92a06920e5744c2af1cb032fe61760b7f33b05159c7d267089e6470ae7bb786bc5a47dc406bf01ba33637224d1fede3acb7e44408ee53f76969ae7f5fbfb1f750b30e2bae6ec246300d2665bd97a21ecab71735df804098956f58162ac1adf42243d5b1406379255bfb78889c820c78784c69f5632fca15a2a423b22bb12a0c050d2c5fbb0ffe9e7acb9a7d55fa9cd74a4dd12b65ff73bcc6702031a590003845d2e5f20092cca4a25e368a4491a36f584102ffd1872be40a91ac803e34e390b9ac00b276b6c20d4882923eb35ae29ff871f9c5697d7236018926dd93e0f554d543066e0e813375135e160ca0c1e340a090322f54f4cb5b18a04fe52f776258446acf5cb763f02275fdc4e8d69b5862d5252faf18f3c4353c331af40c2c323a30a4880b7de9bc4ffc0e1bb6d9b358c6b13681db9a1bbf50726688e236e0d6703c4e8e5b1254e9f6b42fcc60c3c5f79737c7e493de4a7f8201510f3faa6008d6ee58193ac4120588da4ef91e0c0f47b2dad9b26c04d194dc107f46daacdce2b20b71c4a1c386410debb48650ad522e5f816e0786899aaea1fc14eab105553a5ccb51a2fc7076f544f137e38110b5d8e4a76ea07fffb1bb75e8ce38b5ee2f4f77f61da78799fe8e479f6e2374b6ff51044f3e5dafce10bc5a805111fe36deb17fa61cb3803df04b133ae995f44cd3c5046d550ad98d02bd549b3ce702f1cc54424c4ad50258cd81fcac664e1a0d19a87ae7df3157a067f4c51385fc714afc41c5e698d9941c1bc58ba0a796ee5dc62673718a41c8d9e0036395cd8b525488c1ccdd4de32f28aa8d91c96439521cabb1119f5b77547bf78e134f092719254e8910d2b3d55247c8236e16b801cd08df4459fec711e61b3e422ef73861bb9c930b90f7d91895168b5df4d80e1e510e91f6e5e3b6e778f275db913c081a444fed1803cfc04a65804bbacffe832263c76075897f11283c11458dfc99661da5575a69e957825014505e6b07035354d1360575a46024c50af7e258cee6dc18c139f3995293e5a2dc5a600d021f870c053a671b7c49e4e88ac2cc13357d0a7551d6f42e2d52cc5273a15b898884387b68f3ed0f54041609988d3ba424c2f5070db5bb6af9289f01c3c463887c5760066715185089749cbc647e85d1abf0834b79de0c846e6c7c56f15a361331d490c88c1c8290d3d0097a0314298364fb8b662f9de547afbb0ca70cb0ae0a0cb2dcfd3056ba8ea3414228aa07cbeb3adc761e37583814d5ad8c003b1983df1f7dcd169428d5061a259ac65cf5b787c8fb39a7f59829f6673302b8f6ac68986133a41e39cffa07ed2fb4ebc00809d6694788f542c4e2d62101d526af12b395e8131c732251f3f807e8b30d32204204cdd64b1b8daad473c855e94ade0996755a4a20e6a3f8b5525da4a667f7afa7969428a6c8f49d5622283a52d86f1697a7cdca90ef15654ce59e997b3793e818a23f5dd9dbfb4dcea8ebdad679c008d1efe43fb776cc31e3eebaf0d017d5554c88c6f17c604589e8015f820189d357d68354c16b44f548b06bc304dabe0f0f6878c133ebc6dd02f0a5a931a5525d2f4886a647c70b68dd7628406f89231c27ad4b66e39690039937e1ed3d33ba911712a6d9cd2c21831a8a0542af1881bb7e29e33e58659dc782ca52f1f65da4e4d382dbe11f48fd763f112ad7691e942412462bf0c16eadf6bea806473aed118a951c93b1679c7650a89294b9811ca46344fdf5a42f1cb02efc12819e08738b856c05b3f4a157cddf4444c227a3a3e879dd4fc8331b1b8a04c047d606320aae484c024c4f8a74019c985a7af2d5eb89ba0a3696b93f1d1ad23b0b54035a3e0a2fd1b913a70ec63ee68030a8037b4aa6bccce6193b3b74db26d15b5bd56514ecc065f154a36a0889b206c265f034e990a992194b2cb1ac242c6f2901f8fff7b8511e3670a5cbc8cd922128fa141d28480222880273e5cd53d4de72318e7741e32acfea6f90afa9c259b77392c19d9c91a2cd3c99542a83acd074b1ba4b3727d528d99ef7ad5ff0e28ff516311acfef3b5357033830f77df8b98e7d3bd08bc240f23ddd16954358169a9b4a825a1ece6035d07d195c61a3434e2ac49ab2dd16dd9f77396f4c86077e696de511cf813626f405c71140c1b01259fb393e2352daee3ee14ff782e6ec14445b3457839d90c0e987be9cf1c3e031f6501033b9d2cd0cb10360a427696e90aca312e0b234040a3b46c740fed270ad4cc55a901f85ff4b9571f9f632af91d681333ad082c5e3ac9dfd30557c9f9bb07d1dd436b699fc7b84f836f58a016700739ebba44292ab00dbc3ed33aae32a924b8f75e415b895ab40630c718f8ed777aa6f94bf40a28f1d354d02140ce750ae2f7916c65a5d450798a005204102d850fb07de843cd4a29bee1420d0bf763ba27bf5809534e7fd16e9a12ab6d73573abe81252f2f8c6fc77e572df2562b201d9f1efa99767863b4686b7eef07fedc03f281a5faa53b4a004723b67add7fa4f3d4e74203fafc3012a5a695e67890ee76550cb33ce8a5a77ac0d40e701c7b50aab5e4c8c2cb220d56eca1d50f25d7951e9bc2cd6fb270a208087d37912a19ad6bc91a81e334e595d031f8d01abc277132a07b05af4ad31eb0c0cae703febd51ff3f3846ce0349b0e829abd2f51c7b980c04530a381977e6fb4563227ab5adfdcbd3f75a2ec9415daf8f59bd41b845140f67cf42333632689fae8f51b057faa82401ee7e4dc5cf9dbd54353adba50a646c28730b8941460243a992cdff43c4053355e4d8e04c770fa104091c1d82468531230737de5f7a0ce124c5da24008d4f9592ca13f3e454427e3b0bb0c1d7150a96513a32418d250eb18a890cae9cb3fbb8edfd2b96a79727044131925d72a5396c3c000c7f3932286d62b9a71c2178113bebb6876c4e79b37b87ad2a8d60c86597c508fa84623dab67b0050c84d413ee044c7121949689f114d7640f57082760d5ef8618264dc975b3c534116245d471401338ba7dc53e6898e795c6c84330262c99af4f26434e85bc0576ce064afb598ba5fb0cc6c3f3b0fb4be4c15b9327bf2ed23bb87da76ceb43b25bb28d78da1a028ea3f512fe961c31d51734f7aedced3130a64cd13e71b8f8ffc24b4810b4e975d536290169e788b4da3d444b895a2fc4bceaf43bec1df11638c49f0824c7560e75a112c0af34bafeadd90b1a1aa87ba8218c2466a95597ed0bb49671b17864aec5cbf499cc0b941b2a3d64109ee1c4b1a982a373f0850ce4c1d297266db6c8b5427532aea34c0c54b386562c2a23451a886a8d55431a3cac5b0c801961261138df7591a8f948ac9ed14d27535543f3d81b1015222a67934d79bf0003aa45fdeeb65f160ca24db33b49a036afcc377708a65799854e1f3ae159434ad474005f175706888e924d8e8e8447a5e04a015cc5b599079523b1c174ae7bacf2e703dd75deca46922e5a7a14e2019c740d04ed6556593c6c0db284cb1942bd41fd772fc4e7eba402510d4c6b4d6d723b13ab1d88cb387ea5a5363a1b199c84f7941793b0302574c69fdbe2e28b86f3d76c4a1d35ec0718f869e076bf88387ce5fcc02f888b56a554973c7cd1db49ce74a958a578d0b5bd27ab8c6dc26a2656bd856043689d7469f3ae80b473af7667a01f6d167d27f214837e2dbdb8a7398fc40e7bad24a8a932e769b27eda508758e91af59a048c8feb499b509b171f504c381fc320f62d81cb49b7a6ff754423c42e519cca69f46689fa9902f813c563f3a74f8621dd4f23a549a1384d632a2e70917b74a5232481be1b1e0ec736ba91035426f555ba763a67802867620201edcc1cdf1453f260609b6955376189a952b54374372b0d57c279924b7d5ba31ff425cac06be41a9f1384a157d5fd6b5e9c9e4cd8e91350b4053340e8955c7900fa08a81ead5a06560794a054069cdaa92ca610aec63a540018728d3eed619ad144505d1567a19f9c5e9363e1d8293f179c8e25e59aa103ba31fe2f64f8c0082bc06f08ac3e5611c19a65bf3ae0d05ac68f666636201e910f9f84c34613bde30725a00809b1bd4be921518541dde5c5142b02630f5c41192c4bad743d1de29aace5e90d9e0b6c3e320fd453b5a41e12935d5869eb9d523042ad3c97733a8120aef6a38f612abb7ebbf7c8f516c83b22530e5474705d66a0025d88c40f39c9ed7f54c1446910e5076c402747fc0b12db30805cd49f82d67bf2822810b127fe54d4eb174e884cb5d71eda12c93da39036d92143788d3ccf7e54d3bed7272535c39d75f675243bdac184d7b8d013bb2ecdb25d2864a44ea8471c3c556ab694f05f08fb88d8020035218065ca71c9efc9e4de383a2740f43425f036a444f9df8fe99f51eaf6c208bee7ead802e7b786ece93d4ab779ea6d394c8c353f5b8088366d271bad259030b613a267078e740c0e296420fbced181b7a0064682dbd5f883aa9c47e873d5822abd4d4ed8a7c4ee97116591ec017acb1567abf51aaaf466b617b350d6402f0ef97b5910e0f5f3ecaaf8a848cac2cd206fb3c3e79a3e6106a7a32d5d48c3c930aa6015ea4cefa222436a4f9ee854a590525325ee4acd43415aba75b0af9f1d613f220050155fc20478b6bc6f553c1203c7b92d37ac47b7c8ed1855764943a305ab42427668e8caffee01abe0265431abc4a39fb6d6562ba02a64c8fd45c142eb951286140383b61dff8abdda1c6f1aa16f4f36e35a2f5fe4c71d542c8524e37c132f1f11c4f4d8d019318705cafcaeba2e874b71fc0acf9ebdf41084a881ccacc169b7adabd4a693fd16e074bcfd75ee5809d4625eef22741928f151c32c1b2d71c57f2a2259d4f12c4122841901f737f3fc24904f282db39601085134c1fd3f5ac574c1f3635b6e9fb609dca2524842d326d77bc6bad526aef1f8e307599117b7f01909501d6088a726fc17dcb0a4d098165ce1f07596238172a840f6ea6bc5b3b944ed5d96484bf84dfa29ede170f6af41137a63262de208841b20008072931c490331d88fb82423d28c9ad99523ff277d8eba39babc68d13079d923c1c22b62f9d70ef12b85432a3844efccd330d3b7a2c4903ef5378e092035a521adbed29ad1daf989ab378fda625aeb6abd3120de27c7d2a528c920221213fe36a4d9d630a093d87cf0021d794e75483d840a693e0b22a0457848dce5be7ddc218926ba6ee0562e539b0899b82adbfd00a48a19fa33b9a0dbc48410042bbb090ae480edf6cf98153832e5082d415a3bd595e404b74e17a3bdb0c48630dc8702f0c221f44535d598c0c760feec9866b6a2b3d878638396ab8e1bdf434228a7bc1f21c67bf01cc94719f09d1785ba0ab56a9010973121882745b6c72ccc5b107209b692ae894a02c3f82f035e5a342ace22ba0fea4a80b2c86ce9cdcf58d16d6f15dc84afebe3f161bb513c61953082e845d5d9d03f07ecef81f3ab47c2cf7cb7f857f738fea33b2677d1094e131771e0fb3f4a4ebfbc267575941fedecd0f588cc42fe90fa781e32492a6287e66fc1ab957cc35c4350fc40ce46aa22f967ccd30801b5575d877385943f0fa0c4530190ca9d62b0902a71cad6b65dd076ee718ee84d2020b7b2e0282991aae66f24ed205689230453045322a731c8cb34fbe1e319b31b4f0063e3295dc6276567f5114c0e0ad37640d3e88a7ecc02f6c9dd2eaae80e51c7c1e39afecd36b622101d04e8b66a28099dc03eb2e8e05ced173afd3774fefd9d5e5b36fb159367048d8ad6964b11f08c37f618a78637fcef6b0d5c5f7899a05f97b71b672a9bf179f321daa4a524e169dd5a820dbfc1ac5ec4c3703054b8fc45ba13c500e06cbdbb274bcbd96d82fd7a8d7fc73004d63e52a56fa11ac723f3d627de309976d0b1a200fb7e748eb3ef74f128766a8739424268c091861f09f92743f33eb9dfdc6ac1896dc58e5a8bbed6b576b138f9821e248cc2763ec04251bd05738de9f3749eea955eb9fdd902ebbd7ffe8c5cc05b564d1e3f4078cab5355d4cd7d586feaa1837bed3732b27998e0c2571c3951606e47d9c9e055de17a4220a79e3f39386b4ccdc450d03a0834c5bc33dbff4cc8a4c1ce576124da4dc172514b8d0c14406735f39a33ce148f1944fca9f6d427d7e660463b783747ed0a999e5028675ad406e1982e5f18bf6de6ab6e54a5741c914b0b58e4884b7071167a05faff881d437f1fbebc115a039337e5d2e58fef1fc8544044de3f6865d1d9a2fd23fd4197d46cdadd2f60daa770caaac847854eebac3ae47c17abfc985455c0ed79dac4e6c6093b7b5620ebc30ed054daa8294a1a2a67d12939e9c2c4dc4600beae57d6f57319aa78feb2b95d68bda8ee4b24e08ee2c9aa6d807e801efb690f10ae8efe86e8780adfa031740eae118ed41b21ee8cc1936c337ec0eea1074470ef1e63db08e87534112b39f587f83caae5bc7db5d44c2e369a881d33fe5bb62285b4652f24173f24bcc166f3fa6ef06266238103bf3900a2c19639f339258e8f0ed493994f82ffb351dfb2088e3dc66f16606e2e5af48216e425f175daf97fc7496d61c7a57644b0cea7ae77a1628a4c488e3f329dccf8fa908a0cdd1b963f96b2c8fbf9cad45515cd976dd782e3906101eee75135834483ca8eee4a035fcbd3217c7e60cd07b14f21d1ae408cbdd582ef7783b366e9c447315fc9dad84bedef3cc5ede7b82ff67e0127d33a89073cef25428bcf74a77fcdd4df868550c92393fad109f8ec9de84262efd9f2be2249228ac5618095075241319effbb98f35253ad86a2814a147406552ede378eab1440813ab4c4aa88f117c7862d33727efdd511fcce4f01cbde798c27a842ec09a96016e00d1ecf7937b045bbe0bd84968fc0a052bfe0bbf10677df790d08bcffbf9f5c5a07eb23c0bcf3fdb1031053f7ce7964fa34c82d10c38e6645259e9b47072f22382685980114dcf7a7990e2ef78eb8a3d7d385eed35e310a0aac1f3ec2e4bb1b3d4cc40866dfd0f513f080f3e99802ff8742c870f2e205be061a3341d69d3f9277f5dbcd982c66ecb4c2215a623fa860ec4fc642ce0fb9fe739c15a75c39d8d95feff6a2b6da686410d25480c50094e9632c9858c2dc52a7a4cc2d58b1a583c566416428e63c2985aa80aa4d61b7ff140209380ec1b5a3ce1cb0c023c56af48bb31bdd9ece7ef7f0b319ed95b916c1d982e6e8501c1e74212d42ec632555de8a55052c9e32928c4ebc84c1baf32f56068b505d47f0e4fdc7a817429a59adfdcf22c1b6dcc9b3a5756ca52fff56df24592e4f4574571bc85c9ef8434282268ec877ad5d5250f37905cb9b9f330500c76a14b7ca71b6a555361ab5059b33995f983dae855a7c4fb026415a3b4fc3389f96d58d8e307451c9685184ce6c2868c3ace1ba541d1ea57a5c87c6622d0641c9950539a17c1d0970edb09ef36c82b81db75c69f55d8e608baa4dfb087b636975bbe36cbc25c7b253715a802a05dd410b9f8a64cb480d52122999499e38e181794849992967cf560af127e7bfd8fc2353ed5e312c29fab7463015cbd3691cd71a10459cddadc0ac5f2649029229c18e34ffe8904c350b631088f5e9f913a801c7b90d8c642a1692a855ed18b85772585730cd1710998ffe3139622d153c44a2664e00bbf3f3ea40ac2d1370408ac2b65e8a79b8d295090c8b8e89ca2131129dec1879527d49d2ce45268982b8b6eb609442aaf18f585e3b8a5be035c1c0dfcdc07bd4205cf6e7c73ff524c191019fc3e31b51c843fe100f12f7788462eaacc335f5d8bc7707ce56d571d5d229574b5a22b3cd7e201e090d42f560f93a74f3b7ff63d6cfd507e03b308d0280f5cb78a6085dec7800866f93467f7665dad839f1956eea8a10c64d81e2466f09374d5d21d4275372065cf6290ddf2c7685ba19f9301ac9aa1079c17d15f78356fd3b4807aab8f015fe9abc619be01ced26e0f650da4a55d24afd800b6db382953442e36e640e7c689ea9366f584e409e104a50024a5a0994fa030e5d2d869550092995030ed0c621f87c5e2318a98e466e640e7c583532a972c77c85785c42bd80232642692b81125642a57fc007b49d0c8334564e6c48ebaa04bd2461759044236eacc6d188e0c01b6ae8ab19bfcbe012ad8cf1491020a346b8f198ff8dcaf87cb4db88acbd1775e7a757b06021d58807e8dba8d0b46d4d4816447ba093ff2bcb6ab3d48ad6bcea6386509a84a6be1a2556e6787d9f1975423893b14643821d19076bff63227427e91aad539584e58812080e87575ab738cbf529321efc0312c112618d2a13489006ec6201eacc042a5150dcd57405fcbf5a84a45cb9bfc47674404f11780d37873fe197014f709c38e385cb50ae8957450c5216defc35aaf4dadab52a5ff4a242d21e01f003c70f1d41d54631d7993122d6daf6df7b4b29939432930286028b0276c8e3410b903edd63eefaa1f0d4de000f3421f799a9b90f58c725cfe5b1d8028fa063f55c46fc68d08c3fa60613712fcb0a4dbb64ced98d11c38cf931b1704b2dadfc70d288bfd1e624e1fa73b1aad987645fb54ad5cd9568f5abbb9b9b5591ae4101a54b3683db344ba1a10d35381d4cd9a1db287f785b3e2da708ceb00537c5af3543bbddab6ba7ebedd92a96d7d55410077cc805b82b2c8e89be28ee0a3a6575956d1120794c250678bd9c7ad6b26cce3eb98d6259329649f5f21d3b6459dc148ba88fed426138ebaa29ad2dac822c1f0ea34d62b18f7e501401d046d1ebf039bcd0e3f037bc0dbfe46b781a7e86a7bd0c1fc3c3f04afe8577e15bf8a067e193fc0a0ff4485e854fe167b94c6e23b3d194c9e435b21a398d1c95c764a88c862b57bfe99b5ecaf7f0499ff4571ee99d3ef7b977fa9abb3b0e5f0c9ce3a5bcd3277d0f6f7b280fe5931eca3b7dd2277dee739fcb5f3ee9933ef752de09cf9add5f054f5df754c275cbc2d0867dcf28d886e99e54e0a9b30dcf3d6d58eea9dfe96d9f5b407c2b8fe57d78dc4bf1637eca4f8f1bf3d38ff92923f14d2ee4a7c76d31b3142c9359b675cfe3759f5e9ba4b4a734383fd00b5534483826f0fb2fbc99eedb4768582b87d1f7a24a69aa83d40b368811d0eddb9a331f7888d8f54c4920c7ed2d0668cac700fb6349f9aa757fac262aeda934a862cd99c394084d26100475c153cbe64c4efaaa3b768a356744339f09446116816fba12b62370186de280c3bc249189f219d98cec9473b98cec253765a64c461e238b91bbe4a5cc256fc9b8acf45e7252d692b3e4303218f98b8c94b1e42bd94aaef25f642fbe2977918ff22d53c95ce42db216398b7cc4374929a5943bbc686a1ec7982b8f79b462e6579d291f9301045cea3ce611478d1c2543d9015e181f52dd9e145261180fc380335fcebbb156e690709f5c9c0ff361fc042fe3b58256bc80a7f6aac1caedce095ec675784ef032bee39ecbf064aa436956f7322907ab2a13a9db77cf96df248fa8a84def15db9ff8e84e6c990e7618fd71f18ba22f36ba19183777b1c8c8aef6eb7aabdd331bc771f4eaabd52d8a9a4f4e4a2bc645a27dba352b86fa7c4057f4da15bd7645af5dd16b57f4da15bdf6cdaef889548c5e18d65a6b75ec21d178aceb84c2868486b6cf7d52548bb690c774c3dba6a213d88bfc898b7e67be84aaec942e7e55fcbbf947fbc66f09f50983f86e77fc6e77fc6e77fc6e770c6b58fc6861921bfaa252babb7ba538fac296e90a594b460c9025a30468cbbb44c9964cee1217b24f35e9e1d5b64f3db35a9d024a97ec8aef0496bd36f57a815c7cb9f87ab9f87ab9386bebce2c4bafaee8337cb40c3a0b78b6654ea3591c7d7df12fe7150b39c6f48def04ced9956c59b61ea46f9423701875a9b664a8cf97254b972eb99c983b76ad8f36de27378a5b44b50fd11e4ea8cf8d72c79b26fc8d975a2e3c539cd3e48feb43c55018a5b92dc5e4b64c4b2d469291b3459ffe8b0eb1e73eab9bedb51a98339d734a39e594795b0d6aa9b3ee7a38f67a620830d0f16cdbf251059e7977ac0982d05704b198cf1021ab159db256154f7d79d1b80a45670ce7d0e593f31278c210cb4db83b1378de0ea7cb9e672cf0bcd9454b28f149e0a945248e207262c44ddedff4453c1178ea213cb56c82a7fe21f0d4f285f820f0d40f84943fb28ad628b09500759f544ae964340285b2ed8f3cbbe3c23a28dacb83a20930f34d792a524a19aef4501735551b7937fe86a63466db437dd447fd91d091d4fe514eab3faa1d79ff477f6484a706bb7842bd284a034f2d75512a1d9a15cea9c1f1f1bc0e87cdcd8d0d8e1b3835397845a3a3da49f18419fc999e2f87a73bd66d591dd5e500d000a600364a42006cd0769efeae7771b5370b9893901d719fcd5c89bb144b4bd949d2a805974568688372a66a545609232a4b9096db6fc952fc162d352da316d153007c243ca5f698b6d4dd95dad6a12db53f1245c253cfaf5b92328bc7f26d79f359a5bc6dafcadbac553a6b76d6b6b7655e90fa58aab71c903ca8c6326bd5e7d30d9e4f98a96e3aee396b5e3d1998d6599a4bd3b4169ae3bcebb8015e8e7b65715c15bf0e815815636154d1007026fcb6b7ab4ecd1603f4b500b6b3ed3d18b8563bdb9e9cdfa7aa2d55eeca952b57ac58b1622587019f870c6409e053a194522a6ea9a5eeaed4b6fa53792ab20e0a7052aff676de37038629150daeb9617383032767a5b3c3937f86abcd32cf8ece2a07a727c7d7ec0d8783e3c6e6464d0e9d6b4eabc134aa9466f14cda7d4a85e07dda54cfb05a3bb46e7c9af9ba968e70ef00c255ef4cdd9a358fa6c3b5da528a9f988200677f62cede018425ce16e58f1e1c2d4c792e3e8b77fa293fe5738fc56bf1653c16dff44eefe58f3e29dbb0ce5cd19c3d6d18674f25ec79386ef6d4366cb3a70ddfd8d3866bf6b461bca70dd3ec69c3aa3db30da7f6b4e1704f1b06f7b4e1993d6df8dbd386bd3d6db8dbd386ef9e1abc09718438614eb80a75747654aeedac75677b6b0bf862e0a925a5944a6fbe80830ea074d1219ffbdc27b9b5d682f46d3e6aadb7c94ff167f8bcdce5ee49de8c37e3934039a9d7cbc54ff165c4a68724a594524a29a757153ef74d98f608424a2973957adf5df283474aa7ba23a5938e94755573a4bc16a7d312472793524ae9347df43236ef64efbd01ced48460586b18a7c2d40352cb9e1454291cee184c614513506ab0828914b964658a24be80b21205ccd218162a65b1bc22a85563db45896dadb55abf3cd6ca4f621ece96465d9874b80f5c5be6daf27a8dd1d77ccd25265c8c706aa1ceed9a3594d5a53276ad1eb83c3f51fd526f09b775b634ea8264096acb5c6e2be16645282b5af0470ff563b8bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbdbeab5d6ea135cad74adb5565b2bd296527f4afc971e243d487a90f420111fcc8379300fa647851e15c8d460b4e589d11625a3bd92917801fce48cb9206ed90a57db326f95b58b2788a7e51642673f9fc641de4f121f3f9e0ef27ebe6f8605c2f7e903f67769cd2d40e99c3ddc9b781a943d2d606984a36d99a7b471419b52a528361542673f9e963e83bc1f4f4bf9a389a765ae3b7959b2116457772d79efedc008f5b4cd9629217536132247a7b287940b502d9354169946a8aed56b4ba666cb5900a58bc886a087449db69d9ef2654db0a1651501b5d5e007e4dbae7f4296fc991276b50d5bf702a9d9ce53414a877a48aa3504d43b52c04464d4a725d33ef273e4876a4dc4d3200b08d4d861a1960e9b81a98189c8a86dc4f7d5f2474a033a2dc39e9e101c09e5cf0c4bb6da1485edba6e4f83924a293da5fa5a21d0ed69707af75e07a9a78d78fbb2889c3a40ad3d02a5fb1055a2ab2bab48952f2be46b4d09b39f089b7e43828888f8d81d18f1ede9d73ef2f36950a680b8fe58409f4e4244c4c736e206dc7b646f190a9e92c0664a29a95579ad10e8ee9c82136aeda80d2b510874775df5d6140225106b82dd74ea395bac22bac8b51648dd7742994f4f40f3697ea9cd1f8268993c7a2c58d2da2a32f7947a4ae0d1232cf50924c00c8002e0d85575d8d66575dacbf1f5cc3c98439ed44e8ed3a82490f397d411060080220023170000100c0c894462490ac349168aed0314800d4c6c2e6e423e2a8d04229138300e8502a1501084000c8300088241180392248773d4d4019d1820bc7f92341bec6ed4e468eb0cbc22fac7144473179cef102c1b346a6b4963df0e49e9877cf5a4e5046ed62ec75442ae30d98ef32cf3dbb444bff1207aa94fdba0aced08fa6e76654e73bb4e531288a50840999db2c70ccd6c69efea8bf1b8a391fe892c35d34d0c8380a7b05ee6f8c138698efd77647ae375e6988c5614c68b3b88d5509a22e6e029017bb0d0913956f14ecfce28dc8b3fd5ee9cc9090c1de38a51df355431d1e1665a28d3266248d32c27884deb3745e8f809504d4b1c48f48e81ce0117a2c29b7d9bf768a63a31b6e47fcb82e11c5ec34c760e4f670e019e3700a8f39c3a514a4e99a465e4b4c8d20653f071029f94d22668a21de6be6c354ace4117fa5b84014e432df058f6dfa78fa4b733a3636f2d6d7beaa9901dbd6e54bddf2ec4c61bd39e5728f6021eaa4ceda39f605409d370e51fd5cae917f5e291da63cd72430c967658fad322119636567d63252646c38957041ef68c1f0c32b78026c8ea8cc26ed4182c9535b39a1c2ea6dc951efd3a065bc0477f063c18d6f03b86ec5fca66c90f0aaf6d49c22e96b49ecc520ebecd02b961b2ee8284877da5c5da130eeeea9d4c347ddbfb98d4f4e96c497f1cc8cbf4293d615d3292af07a4a82b07edfd6ffaf97fd75c3311914249e4d03670682c235c2b8ff39e8657ba50c324075758ec9221d1638c3500ff87997e20b9eee3945500dac0f88a6d4de3bca6b470afcef9f989081d80c4b4bf864898e17dc9e952b9081ee47dc9e952b9080e8cf601721a057a100f08ba370f9141c93f90d4a570912dc8ff90d4a570911decf6a7d2b588f72a44a49ea48152bf084ee465e48052bf0826ca9451a3d4f14424a652cf8840e467248152bec826b2392315d0cd8a37e1e46912c3a68a23e190a1a10644cf3b92d3a7f2111c68fbad8ab4d9bc244ed19dc0ed4cc48c130dea4d8a9038d76fce8e167e3d9c86b7b346b60309d88d67becd4431f423e101c9895b33fcbb4df58ee7a28c133e2806024b5a81d8d37553a84d9c2c2ae0e6de05ad1e580e8801fd020f57652f0ca48ca30523f18ddd0d94aeb5c601e460986aa2fadff45ec22a39a4124f43aef86904fe805d2742397b0919908cb49ec8c362b8544d8a8697ceede6fd02de87c5c677e3223e04f8c103d1862d9b61fb858e696db89ee8b969feebd80ad9b0431ab67d78fea541251d2f76cdc0c40ffc2c60338c8a4a8a0a792d612fc4adc7848eceb6f71d3e9f0d33c8e893b228695811f9245d8c38700f6037e2c6cbe49ad06024627aa0de19fc791a7f720fadb7311167d62e1a3721aacc385b84567b2f474c7e1cb5c98eb4a0dab6e67381566302745e9048ca81b3b28092b52e25f4011a6e8bde09288073d210930791cfc1af579ff2136776fcc3b6e7d0706d1fbfa933ed708cf27e5cf9ad7d610b8c9be9913da8d5795550220c244e1ff117e42377c8c2c505e30383b629212c494f4b129347e82c78fcef506d591e4675d0d22490c6c38d388fb861e4870903ac220296aa674f7bd92f39b6b520b76e45d2830d8282ed636bb9f9f1b51a11d3fb9b458636bf604c36f910e3fa427c0eebf2cc352e9fb700c92470439559803c79f7df59752bc1d199c678ea65a2c66ab916b9c50acc605d0264bf70c8e58dc7071c0f1d386a84384e6d72e59a6dce907f1bfb32cf4bc503c18fedb29f7a7eab1b706d1781ce0754ecda84e9b5aaebbf56621d4040171538516f8e4400bd179eddbf93c4149e93cab61ed0d557164b21dc19e5b1b2bb87c2c8277b22b5656b7e4ad1b9a4e5852cc9ba669358a556a1b05984bcbd9eb3514815f5641bd857bd027b6bad268f20b2d012371ae85a14f6b7d445bc635ee408b0416452de46a9da6774c7e195d8b8310a56fd786c55b2cf8d5de8763444b500ef608b6fe5f80563d5ffbdf28eea32ac69a45e114775a900618346013055ac024a9f5fd001e35b4f0b042482370f855e50425be08d8ba4727a70ed787a2126108ccdf056773db64cfc4227c84d0fdc5dd916e7dc6d23a17b16e8559cbc04a074e39551879f15c3e244625812790b8e1578c118424966e3c72eab688ad3f3852933616ed2b0ae7b733eb9e91e6c8b38990dd966739540dbeb3d3ba2baed3e8c0946657d708a4e65fca8222c6f73bdccf02d89948fdcc8ab8be4f25721c3a910030940c20b8037b8a73375fc1c11cffd264b7e7ad96c7530697f65a3fca57eeaf0f09725aee017268dca6116988322011d38b21da810d166085e1c7179fa78cb767ebe743f53d29b684eac50cfc52234e56de0ddd8d25ab6531ae927103946b46303208f4fb26044d1a31e5846edee4d1ccf3d39073a4907f6c7c0643bef169e33c95b33bebf28a49603a2d605c185ba70a24d9163e12a397b5164ecb0cacfb415081b26259e03843ab80556a03ce30fc4e8e53de5843dcc93ca476b06df33af0a4287aaf4ec8b2263212a39f7a250c8908a4eb45e142254e513ad0b1d0977d1498b226384557ea68d2fd003fcfe6060964c55f3bca3e462fea0ea46d7bfa4bba878ddc5834f3591e9eed1991dddeaf465bc722f3bb297aab34eae834a5361968a74df0055c280580e7b0a2ddad25f43414dd80cd141ad9217d0d8f9319dbb8baa549e55fae7dde6f9b22321d0810fea73b26ae2ee743c6efadc4ab38fd28d07c9e41f3b25ab2d5a737358b0d076c9d7a1eda36fa25bbb6126ce117e845b728c631fbd02878c16e5b63eb37d247b66163a37ad4286b59bd04cff93a0e06ceafc30bcf04773df482875c195ac8004cf5f733cd57755f8fdef4246183458daba369e74d70d898dda81893222c472bb007b54971bca53e907392aeda72c65def250033fc67b020a1c295e950ed748d0b1cb807a108d30ed42570bf1c8f040b4eb8e3b5a4637f864cbd0a8517b8f5b09b34f54a2efcac01f34670f460ff242b6ddc5624f8c8b7d80acad18597896c6454e5233e424e21807b47470b674b96d8ad1045e05361ba256e3190abf8edfdcb1e8076ee2e654d838f5e342275f33c18f8bb881f2ec0b957bf775d7a37f0b338cdd4ad07b7f53203b47bdb9386c04986c9b40a12d1324ac8c1b0b1ac5bc37eaec188f5744d9ed50f9179fef65b645abd05be8842f67a37e867f83be94619ee0f549a93124def038509e31b246248f2ae4e632a52ac306dd5633576190aaa4680b210b1116059c3ab078a7abb2ea9f91c063db22bfb0e110b790a478a9f0be585a6a8dd150b9d65a597d7da8d6529c99e4df2f1bdf578d80c06264d1f442ee0b07ce7ec2daf6732560f48d884bab129e2aa0514e33740935ac9c46655e5eccf2b4bd67a17b46254f62b4434430148debe518764fdd5765946c1be97293ea8f75683ee875cc6337178753e74fd9dfaec5d1250b51e890a1ac629f6ae771fcbc27e77e4445a33339cca89495059d0b61c3cc3793da7a36ec3f704e8f7824885127d3a2a2df7ab4094b8a14662e909226ee4f2909019e91b867a48f4052ece88dbd679d89098d25d9ac2626d4e9248448f3e9d1aa330c19f72d7cc39d53a983d08fec86037057d6471bb74026b6fdd4054385b6f228a3039df025139c8bd347c93de3d3e312e1448debf45891ba81470e6db6a7c8b1183d56456078ed988cd1b2fd0cd9afcec73a538f75faa019ba2272ae9c2fcadb7a383137e2e4d43bf00e165319d16fdf596cba210eef970d5dd4caaac2371c128c0f56d9b537866edd806c8d9f90d5cc82d99286214f4d9246c1d8b435490eea709613c8d94491a6005cb70a813c1a289cdc3ed170dfd2fcd2340293a58f5474b25d479a1b5737b46eedef0fc49bc6b3fdfcd7a0b1ef9c89d8cc6faa0fa17191408fdeecf0e455ab8869f49fa3fe0beef772f2fb29523baf4e06998d2b51557e6e3a57cf4a0ee4bec736798d162f983b404ca6fa6d6a6f105e6cbed68acdb3e9e6f867c57deb8633f19b4490ed88382d597bd70f43c766ceb4f4f7dcd1cbbfdf827961c104b68751e5a921570a1b1077a45b3ca5ae03e5ec5cc754d55c75f11f8fffb3b00d50aa8a55df096eefd2d1d0e694c22c599f3bf7ca90992d02787623cdc79e6724ba30b260ebb7fbaa3d15fbe7a64959db086719ab9647d5e56c6f51a1e36851ec46cdb29b07203e2148a95a3565af363332568600a7f8522ab84083713700cb8b72ca23360b73d841c802af089ae19186ccff07e8d626878d063e2f6f239529391eba7e5ebd76a3be5522d2a91d048d01fa4d1be9c998585da96b057b3708808d423713adfae7545fd98e683c9eba592a7c70ee83e01dc8e738b6301422ca432ae6416a0019f76da99232cc8df8732d94323295fb1f189d71bdd8f51a11f3072589f7dd4b88b5cb2bf954b618fac34985971f727b1dc9ed84b7aa427d95179b69e5294a8d13d94efb80acbfb94bca740c682bc6077dc63994ef7bc8abcf8676cd4cbd6865d31a03c90415b3a97c670e5f496f3f7d61ac72f91bb1bdde018441a16e6ce806a953e3525bd86e1001d18878850a2574dacab16ca3cdfe3a2c7b71e8171f72a4aaa4106dbd68cbb6ea959a22b033fa7ecab71e5e5a301c1ebe46d77df5b52af3c0d0f4130963d73c858d7db7a2ad1e8954a2a64e7e05506c914a70059675a8f5ac6c4c77c6a9106c567a6d4965eb7ca1b1c3819dfd17ae99a7006a150f6484573ae86ed39f1ebce0c7b581071faa46327ca88d829e6b7cd48e63a4173667fdcae0418b666c322d00b0a53d1b50b44c55c0507ef1f2020e1d79aab28d2a67d6a371848205d8f16e07e4fcfe066b9778c650eafe419f54ed66f5f1e85f34ff52c10c23a18b9a13c7d52dca76dcd1a91fcf3c93ba411110378370da21e13eca4689d984f0799eb13ac3550c1ca06e3c9fc1c1d9aa4740e6f5f3c27c7f4c52dc0628ac7738dea59f0d05f2614514cb5c1d13262244c15ca9da425c5ebe8846e479752443ad1c6d40c914baaa5cbde917a0d415eed9c2812955d55705ea698b04c37751ddc0404f77ff317c05dc7255374960a3f53dd18d59df9f5c79f60baefb95f0addc3a50adbae166b649c2b6107d4df808514d51b8248b55af7950dad54caa6a9ea13f14c8ad504ef467476a701cb33e4d21c4acbce840b2bb5259a09d8d9e1cf8369d44c772d109ad921c516e15c66ec46ea7d44e5d48bf539f5920d76c875ab86bf0f5bdad30f518509a0021fdda052fc2cbf2aa30093d39d39c5b998706bea9b870d887127c976dda05f6b53a6ebcb1c3a5dfb5fd26cbeb3bdace7764524be44d0948af537986a4159c23877f89a8c602f70b3e51519a4fe1dfc9e8281bce4400e41fbc90541e423d4262f7a9e0bc7b5a56ab6d502fb9bb764b03eaae3454827791fb842cee332bd4468ae07198da0850577dde6ab8ebc523751c9007b853990a74a7c0704aa111eff46c5e3924122a6a999f90c25e75c9857c33af1407bb87506357eef2e64a691b7d53df2b379f71318fd1d3ec3c674c27f55486328cd84f44e0c158b059f6130121fd8db2b0734db1aaa8e11fede24a947d049e9557c28696352dda02210643b4238d8ad033da3646abf52a7578dc081c7995467d28e7b81aa641976acdbf4d11172764190e18a2f68f6fe6cdc75c9a1985dbb784533efb5646a203f38906491dd7c700b7359fa4b3bc5252a61e146c6524d547be98f814ad41859a8f91873b093ed0c768ca83127497d907b06dc0d9c3f3fdb1a09d0ab68f18353459073c0c295a97434f7fcc6d979eb3c475200055dcba815fd711ecec47705ebeb225aa54129baf884cf6951220fd4575f86442a80a9fe36b33bb8052fea509adc6e61ce52468fe6390ecca478aa4ad6ececa92e32f1d9a9c59c5b0afa5bc44d73b784cc0b3552b99994a66f9ee9ebc370bb44507e20664f26cd521bf78b5d536e0c0bff2bb8ab1f10236a44e839ca00d4ae6ea3e3f9cb824ce1168dbc0d034fe634b2ac859f5f0d8692c539d1607a9cbb8131bb7e8930c4570d597081cddce2843ccaf08ad325d523887fbc750e47826eab8c928b6638649511aa37d6b952ece5bd40f1e9807816aeb5f51cb25e18c8b5bc826b6cc0bec5ac6f7ae4462e28504ccbc52041b33f8f49519b942ce4a1133d395e0e5e07b2b5297633c5576d415e56222d19497e5e0f4a5c7d4e4f285d9a4de36288ed251349345b869a8de895cfb726bd6f5c5e414256fd967502f10ba35cb19f910ff4b9baaf1fdb6f801c29433e06b50a64b54468897754442c4ae0d93b9a6ccf81a2b61052fd0a34838538de31a0180ab1095a009e1be8fef5f025e6598dfc98c6c5351d0708d2d7a1816889cce5a42ea4b6e0bc54ca6d81f40363f23c8d666143d799e37538c047c816b15751c727356460f9b1c98efed73afaf9a08ebec32e020c3b1e0445bfb3eaf23ff5ded1d1161c81e7350eb8741e2b18688667b9c21ecbd748b125ae3abddc9fc341ecba33176201a42d4f8d1a82d3e9c8adc6126e154df472e20c8a48c20c34b2cf04b321530483c14043df609d6428292a233b8c76d11487278225ea80c015acae14184a53983af4583ce084068f889193c728ab9626972234c7a85d490814621fe901f3325a0354f48d2d6df38499b4f43638b753f0261c7302e5e5a14f2866258fda12200f13bebb09a19cc1710d19f5f5e5a8ddd27dbb16c869f7988c9c387fd97178643992f72cc498480a33c6f1b7997e986046773cdb5b2fe43cd808a97967172dccffd69e2e82cbd1b66ff591bb3de9c56e4837e1aef6f94a39ff9ed0b26690458295fefc71369eb1512ba338d09391636b4ab7623c6821f35f3fd9ec576e7b12adb8003ccfa6505e1918ccd84d11a007512dcf15bd23b6808cfbad7942deb278578902ecf1acd89e808dff330fd405a8e24f45853b7b4817abe48cc7fe4f1440ecc3e5d87189b905837f901bfb9ad6606198f9ab4428e0f54c4cb33a59307a6a0f0f70ae0a0e01477eaba96ba2fde62b655cb5fd1362041910964a2e3ac87fe7921367d385fe716ab49f3dfaf242b2c265ffc9b0289c3ee9c2b6b6a9d6b7343190caf6c7a2d3c8aa5d6f5cb186195414f889362e396f032a653fa771503444c66f7fca1fd6021b1729365dc1e27591ab0a7c58ddd7a009011bac8397e1567c6683f82a60c5979f69916c4c28be511453b940e88c44643e88a6caabc6725103f54389ecdbec3c3aa63b8c4a4394057c5391c180e18de80201bb0213129dcf3d2e5051fbb46e8969282418c36846564c9c85080c80aedecc4ba8944821efd0610a92c4678b12421a938709354fb6451a58f1dd3951080ec7224814125e6dee995e7bc6dd20470a24b10188d7e03adc70974f2ba455fb57ba87dfd22efc080e4e5f155297e89f03bdf639e024416b210de5f90e04ff0664dbf2221c68c95f8e7eae3042f1625338228f9d83a3fff56945a0dc4b6c207546d236aed277143a78e3d9da9a5ffd981bb410355ca2d2bca7962c55ef9951bf784fc86cf8174e79f2005bbb60b59876cad729b7cec4540da77a9c5b5277b1e38bc34c67ccf8670e1e3c52d7c7201caa28fb54ccda360c97a4c4798d3e95bcc70b13f2788bbbe5fa33b25499636ae98c87445a7d017492309dd66c13d4ed765133671d5cc432fbba7e95a51aeb5e8b4df5a8a01b787f2b6698280a14f58a82ee785589ee180a05f17228d6f6568012c5992cdc309f503571c934b6003c1878b99c9b1825727a2cb5313e8b355fc1391171975e3a47faab073d45a4cd9e6fd44f8404df5730942954270c2c35bd5d0c50cf6f8c5531019bb5111e6b23866164dc872a3b068e3e210f7d6396d4eb1f0e09ae38a529be6f0ad5cf5cf28a16d110335dc1f88b2dd04c5f302c235436f6bbed544acd989b0b11de2317a112e89f82ca8993a52651fa601ff57f8243d9820f296e82174e64312171ecc0725726b6a1adb532a51f4d05da61a150c1294ba076fe9f93af07775621d96d4c214d5a0cc5f0094f048e7175286f155a2faa8e7eae2ac7dbface32fe4d9f65c8a97e59b364cb02980151ae2e3a4235a189e72ee25f0caa941fe169d370243e961ba66484d7dc6c98c10db039f3a4fdc4ceaaba9fb0ee4174b794f6b8039e730db1c3a1dc2ed7d740c54eeb65825ce528953abc7bc96ce7884e4d5f81f7f7ed51c8dc4fc5ae1f41eaa45b9117998533aada565516d7e33c48d14c68b7399dd2147623b1de6306bb88e4e81ad7f3756848aad3488bd68ee2efe62c7417d5c9d6d6b83c2938c3daa8c0f931716ac2af32f280caa26331cc0e59963460128143b06a88fb10fff9ae4008618f38fc696d90014949f5b0d833ea05c1d8f9f142ec6d5a4346523b5e81c47d9f709c6460cec452740ead057efef15acbddabd6697a00a3e58c688f805ab0194b21a037a7e691e8d54700a9d237303fbd4bc313da7cf72a5a340bc86a0e8e8986bc591da69c2914fddf6d31c6e5740389c8f42e92bf7b77e6528c68b585116c61044cddbe90469bb0491a1e98472aa992b40f3e47da4130ecf588f17ffcf53f5e814d02c179d1cee009bdf6c26703927919b97a30bdefd1dd03620509ca6f19dda0f9993a2a3b5aa0041923c4c0ca2ddee43fcf475db921adb9591d9383458134f35e7375fc8707111854b4478c565d4f1a2b56adf818df04db5bb8fe73a8a23c54d6fb07487b86138894fc8c3aeaade0d5256bfa498bda0d80e99b7f133c3202411855f9271b6d20bde5439c61dc2570e7ae2533d50fe8f0b57d7271c05034012d735e00a30379fe5d476a4d7f4948b2351d6c02e50cc45d4b5566d66b7b7d9157fd582d99fb0af30556d0d40a000502752a42cc82f7a9bd8baff369c13f467e1c252a184d8eeddca334304656e41b33aaf105b61abd679c5bb458c53fe912c749130c5b4aa47ab9e1ea5f6aea6ee201a71e0b845a8da0a619c51e1cc3733ae625a756bd922d9c5950b1cb12374832192774b3432bd204bd1b6d41c21c5066d2b395224ad0aff6d899ba6b7aa031ef6a6a98536f22f2a9b9e3fec98e0272431db0e40db02a5f0a4250cea9b04aff057032e44327503ec23fd652c6a13b6647f96fbc76ead66b53b1fc71f8fa04197663eb00c59b72fccb09421b819adaaf86ed259401ff3ebd3c789cf70b710d800649a00e9160a3b0f1732e3449f394e1af76dc4e67fd5ecb27e3a96c2ca94516324e118b85bb1f29ca4acd3f0cc4646dfb08b7543ee769382c879e8e05f91bb1d32272c209da0f4c665a0ff13c5ddaeb956a6a7a44ac95544e04549369468a9c7491689d3b75b983fe8a3014383de67e71e31ec4aab86ac24840c25e2f135080349454f58541f389bff6c5e1a582a8d3558408fb9e990118d66687827b813d90fdda00a22ec3cedcdc2670b4f446e2302f8f957abc132b098515e9d601f0ebd2e011e1292851c1f272fcb4e05ddb019a231fdd6ee567e16ec64f601aeb6aa5a05dcd8c5dc342a0a16b2a170697ce83a664f4d8b312d1d9059ea98f72c3df5b05589e794a7500eff356a494c5d2036ef477168da76593d2e7cc6af603d9792e583d7468cb4983005366205fd498a8147329af01493f6cdbb02c76142e64f24c088296ce312ee6883301c9318c7f764b2c47ac283f0069ad24ab854e54834597546088beef24e73fc3d5041cd5215443e4825c0739732504c270c2df9cfe15d271880ed4b51e272534fb04d8680553e56f889f62f2b525a44f125ec42b232b840b7601e2ad7fb55deb52dadb2336c3318fca8c37a1d10c13bea8a90597bd9e2c8d805680daa9aa979fdaf8956a88e640807aec09c158ee68f4335a8d15575ddf9dc0b5982c1551e85f187643c81f070d737cdfe782e4e49f260af4b57494bc69a8fed6f0d43df62f335c1971ad747a5b301b84ce96c13a91f0f4f36c04028008d4d30f64e1fbc389082f74f11bf6a2b1dfad3eab21f978e9b2552dd6884a53aea66b40c3326dd5ccbce499f834d3150ac35428a4a80e08201fa9a36e90d14d3d012d33456ae98712183dddd30174d563139ae5838cf5a5ac45f42284ccbdd96557f862d67c1e93c4decef0b23595d3944613b5e412186265dccd398af5c44ebe47d2539b635a3d08ebd0175ec11da186553bddcc6895457f241e804df37dffc06e45881d30842d6d2b485949f762ec5eff79b2a6badd88a267c90007da30635958661d1717296901d8e45354ec6551baa43c69bd148fae2caa5484c9c80968354ae50427d147c178923958442c387f889cabf9605e7f929716f622ed44df41b65263a279df7c9ec8f82a98332d30b1c0a9012877e0c7cdccdce2a8c7c85537f561b7c26f03b5916250a05d9047cfbe38c9f6fd609f7fda275c29bdbe7a673e6d4000c88b8a2a8f2fb0093aec388e756a62df94de7fb1734bd1d9aa9e75c7989013761df397c199c3e597bf2f9e3d9c3ba19cd0cf96c249c745ebc991ee8d7a7136a97cbb4947039f2a84775132fe637cf80e074afd34bc6d35b782fc7c140cf932ce0e1dce74ccf53edbd582dacf14036d0bf65a33516aee24726ffee926bf0259886afd00220bc1ae529855a376b4868d358b9067d9c4b198b5bac00cabe5fbaf3bccdb3986910be7636b358f4d1ba0feb01506f7da65f56f1ac5582ae5deaae564c10b0b8db8e001c5375884a42fa72f13b11724781632cfa09008400fb4e92f8b04181a4c65502c635a0b024a030b0ac18ebb8d50a9d06e30af50e417227e27f4badfc97d4fc647ba4c2f0a56345d1f84ee42a9c27db211ba6a5e01c26e119be4b29c293b299f6b047c0de4a89defbae2d2be11338c10d620ae5bec05cd87084dc469a6f667d223612f13c6c595bc7f99bc706d1c2c4a2049e1f6b41e6e8e0f3c5f04891cd4d6ce791a2ed8112457e640e66d49afe1da3e2ea92e385dd901688304bd58c73cc13f203c1cd4b11330d246a1258bcf8150586d185f99b32dcf51032cc479700dcc9c9043b934551baabd13c90a20656d4194924a6cd3d17f5d3d9f468afe61bbe100cc0598369580ea261194f8c573b8f225bb86028bc01c495128433b18ed3280bedbe74ad158b791df6f7da17418a4a949c782b153e057160cca019f9cead0754387f95cc9e5eb660691f0814c2f68de3e313fe99abd7e6399529b5e9c6742344a79013a22bf853587cdd3c298f23ac4ec4082875a9822401302595bdba98d002078ffd9c74b8c5ff898fadf85b27f37b1d7d341a6507c5de39a88b43644ad777945754ee5d220e8e4ddb24f3c2badb480a1054c013db800f052788ab90986fce4f564419246b070c668fdee3411c25a6cc48d640cac702eaed6c042bad74d97903a5b51d482b4ac2bd52ad7797a11b6d5f368ea50eacb07a3255633a6aefa1435acc1d900a28f23ca2aee3593cc0902687a65f384fa5ffd8be9ed04dee65a11d344b3f70c4291ca9c2cca59e2a8702bef49e23c7788fcd5570c550ec168522d919217e7dad8819b7449ad622cbd528af7e69fc2ead320b04800382dff1e24b908d9689c6fa265057a5d6e79a6306253dcbba3b08e84d56e71910d5d51876330854742420edea075d98643d7a8097a92fa751488a265c9d5094e9e27bf1fa87f5efe028a2329615424174cc8ce365c995fe122c84102f8250ccfdd6bfafb7c1c2e9d2162c697cd73de0bbcdbf25844a3d04c565827c7a9e2923a85c2b81fe1316030002abb831cf812a84ba954f694e2cc2927830e998353a2471e3b9c1759459f3bc48a3bdaa765d58a1f0d7336d1f8cd0a0d3c0c8fc03f92260e001c90129f67c1401b09a997ad410abb272014b297a48010f38dfedb5a739548057fa495a471e1d092812f541357290397531bfcdbc45f2bb8710f42a91ce61c557e91a80bf1c2fe65023f9777e03b761a746734ff1d356e162b40cb8a4b0a3a7cd64640b084398b9de83011ebb0b6e96d05e0a091f0b5c301c0ec6bc0ccddd78e0773a6dc9cc6f60603fc1f900f647a78e22810bfacdf901959288230fe828d7b146a25dc3a109b7a1c855d50cb3566c09d31ececb440708ea404c994d9aa45709aa5c7e1acd161d89ca39c91f601a80ae49aca95b8ce623512430222d2e9033ced9a0256c2c09442bb0231cf6368978d890c79a8a2579188d7e0d20f3891a6094a46d8533368d4a82d4e4cc02fad931645d408a59361db0a263280ba7b6540baff216efa5a6f3a30807de336ba040127337f02e1b853335633ac60e8a512bbbff18acac3b5c37d51f40736cdbff6db81503c087c2c69a4b38565c5bd99b05c7d3602822126a6477db724b29a594324606ba05510662a05075f3c4b54b3689c93239c19d80abc29d809b5242b517675c126e15e29cf3ae8f3381d3123784d3f2a3991a742bbc7e4eb229913d3540214cd4f5fbcaa35edfcca0871f86b81fffc4ef6b60bf5affceaa48c13a58414c825703f54b9a9b34b90dc3300c3fc4781c00152054fe896b7087c25e5686e9b2346957bfb634695991c04480b03bfba70676671f8742982c46087ece3b14c31275db9da6ba34658f4f3cce15638b71a5f9b15873117c522b5eef7af029323831f6b73bac35d5e013cddbd5b6697a1a2703b71f5a1a3600179c5e75b326e19af0fd269799e93213b88b5fa6571b7010a34958cf950cdf9224076858cf5a091f638cf10933a8e34a7280d7c31224eb10efe2aeb8f8e2e6f86934d74a5a49d9d3487317d47369029b20ba2ce4e591ee01299090e932a55f46e28db342aa4391bb910ec55b07c3b024b2bc1e7e0a9a410f4ff430b43bf069fe3e2351e46e941fef306ffb7de2ec392e0837818082848e78bdf65bb6c96d77e1d34871b43f61b89fe89a1b97a2a5595c026eea681c001520558e46dc146eef5bfdbadc78caad2c904037c5df75d2b5fe4f97e62d8a5e5b8a29f2c2ad8316575a4e888475098306478cd24ff6928014d58d1d4fd8f0d88a7212a58b0c1a232db60809f122ca09170a51aeb48471c116060b4f579494215036bc62c850438402634400e1479616624a0803650b7324a7e718a2eca8a46648520b213300098d8c39f2420d5b5d9e7c8d85a1b1f3254a009d627d5de9399d627d5d8d4b1ec7630802f4c7720aa050aaebe708b6ddf15c721dd452f1527d15b9476a5f3cc0b0f95ac115f585e3c3218c1ac1e01082a1a1df748a05e6fcfab4feae3f423921755d412104a5215ea817196a90a185075ab08cf1fd8108049828b1c8bd6096004002dd32febdf9de108633a3df177720eed5e5bd608ef47bc158f57befbd392a076d9d90b9eb1607baa54cb7bf75405758bcae3cc01fa69bdce0e32dd4ebebacb3e07fe09bd8087cb0b46306c512dbc73344800b13a6242c6656c8d8a235e9d102142952aec068a08c182e2af4bebe3411c95d96386181218407151c388ae85312583774617999a95fe6e2c53470e0a2238727685c442932c71c15153118585a78b2a528db10f5e51abac9f3204a8948982c3cc08ace103c5db171b2e5d4c3ab899713af2441d545155c5395fb658e5be2b8532caf10745da4638c31d638b0cef86bc6e245a4dbc7f20a72bf64ad1ed6778218ccd070440b9424438accb17791e0f5d4f3ee790593134a08a2e584258433451f72dc2e1db419e3f8187f59c3e39544b49544a9298411c26008d5805a71654312293abcbabc0420735142448798172914a858116e3ce98232616bab0a4e94714b8e1d292f8034d1e89185890a241d3868e4f0a42442992f58b52e156eb296a0dd65c21eebed2f493afede7e5f56e818638c77d01d22bab7fc6204c607a25c7ea1728163ef9d4eb1be78f0a5452fd380c59584e5a5e98877e949bfb13fe997ab855bce92df0cca4fb5f4fb78df7b332ec72575f5965c4ebab563a7585c413a4ea7585c4e3d29a43a5db5d61a07d77aef764f31a8194245cd12225a49890c295b8044d1b155a49f6e2c5d6b9d4b119783d38e7b8f14b7cda5b28017171a70fc176bcb4cb79689959292254fe8531d256fce546bfa7d545f7d5f139192b5538a5f246dd60ac254b58f0ddc04ea95005402da2b26753d26706c77660ff5e226ef22a5b5d4f57c0f198a6956b51eb092597d20bb322bfb815eb73e48b8d9c3f1d554dd3e89ab4c878f0a3e29f888f109e353e553e5f3e593c407cc078c0fd89eddebe3c557f029e3d3a6de7befbdf7de7befbd63dcbc4e216cadb55a1f3357bc4edbf6ac8751a4da537cc4f89491f5424a29a594524a29a594524a29a594524a29a5949234289a904ebf3605d5e973d2b4463a7d1bd50dbeb9b9b6de581f76473fcccc9a3033d68545023bafa09c57537a56d5a9a9f2a84eb5a942551fbd32d9adaf4eff76d12d33601808f16a3785e8555058adbd17e35cedbd18e7ac71d6fafbf606655826bbb6cabe0d823259188ab3d9b575260b457136a3d16a335aadc6b9cd7683837170aead383738b75b4e8e8ecece88c7f1da3ada6e70706eb79c1c9d1dbcb3736dddc9a91fc218bf3d27ce39e78b31bef862b07783c0c0af152d771162eba244a8b6953fe8f1bac9eb9a7f9bd2202b4dfd3d39d72d28e196954bb38c5b2accb64e4cbd8c1f0f801f2330b2811fda48da8324af6fcbd26d2f42b995a9ca227214b91e3e7f3d9bc0cddb6c0f92e6d84110acdd6eb79b8ececd83b4bf791b6992e8b6d7b308806feedb9bfc763b1a8fc6dbed76dbd54a93c4ee569a24fa0dd1b5e9fc0d69de3cad1c800b6e7b3e019db795463a5f2b8dc4b71d8d4763d7991d8d5d1cc5d90e00c671676767e7b673cbc9c9c9c1d18065c60c969913ab8c901a698ebdf637d224d16fb7ce7fb4d99e3fcf535b9adcee744a1c92e775746ca599f3b7c7791e1c5e9a620e691e8dfde6c5a7ddc6dbedf9df663f0a91186b3d702d70135713f769c913829ac0bd529aeda7bbbd38eb3177bb5013712c7053a4d9d9157198651abc590fb7bcf17ac3dd1439ee19b03b91f384b85fe2b2b84c0b4e08b724cd17a03d63c59f6dad96841c06d673b628be6e649f05daebb5d66a06751125adb5d65a6fb64fc3be7aa3703370fb9a2c9ff0ef4dbe69efd3a5482b19bec513d88ef9beefe3f1783a0aecce9afbc97fe2e183cf49d1ee6a95f6d56a8ee62edcdf751847f87d1fe500edea46dfefff489376f5ef432bd22edaeca592be2807a80709d5382995f3361cf29fb86ce7ed07dd219dbfbd0de7addd7d6fff86c43d76f7edbedf2177f6e77b1df286a45db60bba4337dbe7bc8d1c89eceefb1af9c1eebea7cd4891bc0003dae1fb1c72db9f0fc4f7b6cb3ede8dcd59f0fe28ed9fb8ecc1a791d8eeea177e2f86bc8f3d9491e63edaa5f83607a5f4ea2539afa0945e9db310100478bc5da308afdfb350787677f5429d52f55ee8ed1342faf404eee67eb3ac65c813a2e29edfb46fd6bfafa287254f08f1c30f4b13f7f088d7c557d1c33d9bfd9e95a018ea50872525cdefb70f84dd85187fb03b9a6bb7165b4bdaae6256b118a5988876a0b79c33bf406775a4eb398b1b638c69883b79fd6c0201d9dda8690092e2f5f18f8f6915bdc2a2571de342b0b3204025a8640adaa19a42145c607f2a2fec0bcc0c092121d5ddec10102e0a37753dd6437e59893b9d81b2f248ec1bef4808e901f8ec71af15b043d5eafe804375c9fe804f9740d86808d61d1d7c73a853276a00f0cd04c868352110cc2008fe3bd1d9cc8260ad2008667097486a153885a4825848a5fa3e9b26ff897f8f03c29dac07cbc396e067b28620885e68b30f860fce6618d89d7d1bc9e9590b1abbec8876b056f6077c107c5c0c20f820f818800f822078e2ebb3cf0f7ebdb21e660f3e686e502c8f686f867b26290882571d7c10046b4fb685884a9001ed50dfd6847a82ba8da6c9daede66d592985f37a78adee013d48c874c9ea22ecaed6767f7faa2c68f64fdcdefcfdddfd1bd978877838184084bfcd23dad565bf29e085e215657f657f81ddd5bf35fbcd08e901b8f81848eba7e18f3f848b232e782a6ed2adaeff9d9c9058fd15ca88f5604b4dbb90520aff08fd7b739397d48a7aff13077187ead2eeb89e1e710106d6c39476f2611ee92a9627b08e76b06f7155b8d6afb343bb483f90e5d921ba755d8ffaf91dc2d58a5e4d290d42efbdf7664d73ce3ca46ff5678c31c6a62e975f8568074a9172ce99524a714a78fd295128256fad63980c14325038b82093a5e3408543530faf98a1b08fcbc3765d9696c9ee3e7dc2561c5d9ab4ffcc6d7badf5be43387f67cf1f87bc21a9fde1a5ae81b87fe6aca4f571ff4a23b1ac54b983a591fdef6d69543ff75c5ecb24bbfb2863125d7f88b19b243a7e22fbe9920323c6649ac09c3aaeaf2dfe70369b801593d59088fb73fffb2a55afd548aff85baa6199926c8d24bb44993e9d499cbf52c41d0a4b5de252e0e6de370c53f4ef3159d6a806c09fbfc4ed39d32c9fc234e7d0b3a71fd607492babc03ec8006a2dedb1ce7ac06535f98cbcd99faaff96f4be6eec58939b946152f696a45b3756d1e4b7be8155115a3c06a7427d5b0377858a9b2f169685736026cc849dbe7aefbd39056ecabef7e2a678c0f8daef02dd11fcbae2a8e0a6e08d337202779cbf7edf87f5c558286c824b4a29a598528cf1d30e144785d7c77705dc145c2b16ad8c315d58604958013282f4f8ce98e80308288e3b3d6548757394f104e49411820b1d104c767a2a8d755aa7a7125837837a5e4b575a0d04b650e9b86105275a7c88b1ff63bafdeff45482a1cb84a0c83c5e90bd7956542888772e193967aca0e283ad2d1a96aca055e40b0923cae9c595274c5496dac86828d1ec8c1f1f604383eb2b045c8298e9c00213a9ac0e68b0a0c25219bc9466d7a81f5ae94ac60932b85825c96edca0554d46929a703a3d9544d8147f5709049c801c0e59a7a7d20dac1eaf17db3329cb19a38ba9c5928b5349a945ae49a7d333a94cff3a3d93be2c8fdd3c7a7b20faf44d7a8232bbed8ffdfd9d31b23aa5d269af7ee91487d44ec0836aede30ae560fce0dd7b6ffd6b6f162fe74237be15400b1c5e43b068f11146858f2e251c2841d3d4e505346d483361c02e32d186a91c12b539246ed2ac1c928a5ecbf0deeb0363ec542bb5d626b5f0810884dd552c9b0804c6328018dbc2e090d5c64c0e4ea82a60021634362869e384020ab32b5a2ef3442312b46718750ca149df34edd70c624d5a59be3c4a8a08ba3ff449f35e59bee41def0f3573e65886c94cfbadf5fbbeaf04d13b5740357179a186534ccd6692c534a566042e37b498d1718d1c1c6e9d9e2b98f4b0d37385112c6b90dc211d6a9da5a52b120c285847aa54611f05681106f7c77e63112645dc9fefbf12047a97491e0bf07e5c6445ecfbf37dc6ffe9c7f9f36ff2f1a7d840fff7bb44da40ff677ddca1a93b841370b34454d24ed547a2f7c782fc3c16e0d722fefe58eb638aca5a2d26dcb5fae1cb4610d96a45557b6159605a5df4fe34a0f25751143eade4c13f0bab53b814615230245e9de8fdb12045f8b2b7bd05296a2fbe7e132546d8fefb11fa675f81d4fe907f1e217e7e13253a507b891214a045b4a7002dbaf91223782c0069116d048f05b4c7799c120445376503acd3fda96f2339c953145e9202b4c8056d6681ecc1bf234684236a25ade4b93ffb906492913cb714b1cb71a7491e5b8aa0f74745d1063515d77e3984d4e69274ba5c0c8c753468470c5f8c85ec8e62459df0e54898165a1833c379456bcc922d688434015f00401581978197980b2c98c8205364031b36ac20e6e403a82d45f8e953c7d8c5908e713a2623ee746db476380e1d12b236e372e06feaba79cb235d1f65a861f9c86f4242bdb661a1dfa5a27bdf14e2519053e2b44eb1da20e926e740bbdd4648cf9d62b591eae6ee0718ea0c06a60285135e844541534585895882093564f450b092c5c2e24fece67b2b8b34600ba4104c9f2cd0e460541d501ba6ce3bc56a8324a64f165ae70a31b46e9e5e73c01a0bf1648cad11e2f61b0b73f182d3ce81763d138ca5e9d18d91e943c2efdea2be3cc6cb97a58f795919c6a6248df55cd800748a3556c2988f6b73001023a2f560aa08ad68c11b1b243d36561f1b2f6c04a0268bbd564b0d143555bddab0a690c4c9e54078ff8e76f7a6ae5bca43e9bda08a598b91c4b8acd65203c38a55b1dc5a6bedd34b815939e8367f757b827d9c17af4fefbd379752fa892c267becce014096eceed36c8a006ee6e70921ebb92c51ebd8cc2870c7cf13a2fae8b8a4567a5027ba745681a55862553afec2f363537f36c58cebe2fa737e4cda6ed551e81906eb286e6aa19da1d02295c044d5ea28c4aeff3e8d4a60a222f5120ae8fa3952d7df3503e8151252d7a58912140c09a99fb8e9fbef3d21d2d90432e8da440905744b3be03fa1fbf761d5a489ef4fe80760a097c8955040afb40306c25862533ab51e36b6b69fa8ddea24d7de7bef0b1845ae7740c5eef5776d415765eb7269adbd449d7981b9caefc2659d628581e97768d49aa1e30abb12836b0ceb29e9b870ae53ac3022613dba191425b41ad3ac8069eef99b2f2398a912a1832f23c4d022064292121a5290dab147e8f8c51b3ac6d80bc662491d63311a1de31e3c2f23c739ac1163d1f1045eb4d82956988d5c428fef1f7bc294942c579ab1195c639a30185c58a701d3cd20233587af30b1344c5870a5a96a81e3b2fb0c589a291d778a95a69782be2159228599e9fe0ea8e8b79646f777a654025d9af5f1ebd2ac1f3ed1ad2169e6ffd0f4c43442ba399e01eb67ba2670bb59bf032a6ed7a511ae8fbb38f8b49b62075f87c8f9abd1fdfd5ff8cd2af8b40e918deeebfb9934c26f876a900673e6aa17a053ac334d9b92b0dfd7cf9852fb192c670e50d1f77e703f899e5f8b381a65f6fa987a1c0d4e675cf46d06095699af9c9dc7f999a913a0a614c85437c71fa755aeb30a34ad207f9fb7e65ff891fa3319e2d36fa4f9471ae5cfd9e8fead8c1ab130dd993367cee86c65a3d3457d9fee13e185cef8da1f37baa9b362135ae15430c746a7fbce1f4bfdfb5efc7468f0184fbcf54c8975536775c5aa7cb045ea0c8b2145a2dc68c324480b2b76d0903165634fed1095a6926452a0d4182e55034d8a8a88f34556833d49603aa17ae4d6f042385528432687d20d3a552bc6f088a1e3498bd4909b931b9d24a5630cc9f0482d8552caa820baa8a69ddd26c74bc7ca8d0b270927e4d2e4c6763a3da7a63a003a3da7c61a183469a03eb09d9e501eb410561bbc0635e37c32728e607542b9f8748c1c97eea7d3f309cbd30c2598d82640c8ee68ede58d077de1a678ff96b79c3fe772db9d26ef2dff156ff56fb5d6ce6f2fde4c71e409f195b8e409817974b6e57f4bfb73ce39d775eb9bf5eb572f2fbcdce939820a61a7e70854a68dc03e9dc044edda84ed9934b17d7cefebdbefcdf75e1eb776434c25e0f90136842da90414df6a95ea29c254b72610d0dbd34755b7bfad86d14d119bd928ffa67657b1558cb3d387f1d5e9ef3b6340fe4b23703f63a3bfe52e29dde18b459b33ce240a1b819e6a00fb7a96ab81db9e3a01d86dae74c600160bd0623d545a6b6823b8f6eb18b0bdb97256ac58090b3b7b742959f2848995524e4a4e4a4e0a0c30c00043aec9b6b9269fcef83669926308734bb6cd2df974c6d7e696f05b5e24dae163a1873326897a58d9f773ceb9347944f94d1e05198f39df2b38f7987287c09c6f16987b70c939e32b370848777af680e196b9b4f6b63d48d871b3c61e1ddc6cd6879eb307938e358bdc95170182980e1123b27a1510d103a18b8eac7dd3e909420c3da7d31384aa514b2be7a4f62fb02f33605f6bc2aaacaaaeacaaa09c48b26865d9d2caf2d5957531ce59eb6fe3bdafad3b6bfd7d7b83dfde202893855b0465b23014c5190dd368d7565a288ab3198d560331b3b25fb3aae250ba3dbbb54c76c93631d925cbc33ad924ad2c5b5a59bebab2a8aa102354554815923a5591d6541715694d5daa491a605c1369f6629cb3feeef7615bbfacbf6f6f507665326cabecdba04c1686e2ecce66d8d699280bc5d98c46abf1cb39b695cf6835ce6db61b9c8b83836dc5e1b61b1c9cdb2d47e7eae8605b7564b719036aefc9b9a4a9b3f9922650b73bbbb337820830feda712e47bbb31d9bd5da7b31ce59ebefabc24de0ddb369b92b7e52091e014eb2f48a2b3d7254383d794c9d3c9c7253383d79249d1f2039792041c91b47b956cc41e119cfa8adf4454de69b73dec5c0eb87f5525c3e5ec234e47adcc4e27ddb8f688fe756c859e5b0722ce498e498e49ee458c0f5e69ee49ee49e540cd66dbfab71ceb8dfdc139be3927b527b8e0bafddcc61e558c829c929c93da921e59e9c2008397920c99de70eac1d4e72597ac5b9a3c9b9a3ca6e000992bd6519ef6c4b1073102f488817a63b4441093962029418bc3c40d21ec0a045b00384155a391c8162d248414f328c3f80c8ca1f29ee5d72bbab1b470e1ad72909138d39d9143e8040727f68cf447768df0dd6314e41e4207420dd1fabc531c660199442500e1f39a63e102151c23188bc7350e51c472ec67b6807f19ab25ed72442138f5e0312d60f2baddfb29e3b5cf4b08a50fa25afc51d9be8f7f4a04905ae1f386fac396f28710d62221e05392a8fe8bec9a3e0ea9bdd55a9ccbb431f6c5353bf43b2143895d12ab7249c767a1504f455cac784a5182f4019a215a6c8dc69f06484cd112c165d382832839a6c54399b7e77362dddbe189fb601a5832be78d182cb478da1853ca9df09d364e1b5b36b274f3f64c4586f086e838c8aab105ee6c3d00e94e26aadb98ff8174547c7780c4feecfd59cdce0ea2883d3fcf9b08c74c53f767ecb13ff53908d373a727075ddd1ca3a6a674533c9af3e0dbdfb737c0614c9411b2540102a508192c4e594a72984253f4b9b0f3586e074fbcf58c9d9e35a0d4b87aa1b5fe01e4c51495146af0983ae3454347d79acd8f29ad8108e57ed4102283500da95c2b225ae11722e81f55be1ca97241c618224c3d2d5e609012030a5a376ab8f86accd0c96ba2442b3ca3c777cff8686834b68c607961e025d8682ce1349e6433767c62a850108f460898f7e3879e2143b4c2313e7d6f638e04be3b3d67b0e8a6502e8a7f9d9e32d400ed78a7a70c30dddcd5e8199ff70373e11b7e2c65e5252da0d00f2b42ac36dd326478014406d60c20bbd3339b05a8b42eb6352d64332508080410004316000020100a8703e2704894a561aeab3d1400096b943e665034160723914890a328088228088218c610630842c838a3d04d59017debb7716240c46e6a0eccc80ae3a432422ef572eca94e4f1eba0b0b5a15525d72a92ec6ae6e1890796ba6180fa03689c55555785cdfd62fd74b0bc9510376521ab0d9b1e76761cf1ede0c23cb7709b0d6bac4e5590c8fd157e82058f8273ec4657fc58876fe9348f011cd8b4338cb4ac21a70dac31cf89a90ab308f59559a43775e85aca75cba18ed30eece29fd98a1a68831498b6c94fe2344649e46f797f5b29f48bf2c864b4a959674e40fd8757f689d08b2725c4fe292aaf629a17a6a222edb714f9d45cc9190015203d55c04b569c85aa19da7970eca1916ac3a541e9b6abf47688e14864b14b733a5c8419baa3955dc8788118afd9539c82fcede95555b8c2be5dba70219251a02bbe581140280a1c3f8ffbf6371eca98efa46f8057e307ee621db776a1fcf3d04e8a1846b29c8286dec3424bfa708b4dc8c0302f15161007989c8780c9ac5acd6790c31fc44a44c5d051f6be200105c73c680671657e4bc9b6441dc17c26dd6092737643f455464b14a60ab8ed0b480847674202dc66879c7e3e616f08b0b42a25558c6035e2c4f8984d8f06310832181624488a746c4ae62f4bbb7d9af3c84361011e29d9e7c8f7781cd201a1e1c833b819096ddf89478de50e37049f1bda5e6ef37815091725433ff1a39cc513c209d4c0d2a29f169e35645f232c1696478ec6bdc651afe079f190dc93c14ac591e8e90812f95765944258bcdbd19cec1f61802cdfc7fa47d619b99a88b6babaa96ab2bf700ee316cabcfab6eb95d02ddcffc8556ae3c2ce49e7547fda1c20ebba8be80aa5fa7b8a9e8a82f039208c20c62b58bc446dd2d3280e13409315bda84b2ad190ff65df683efa83bd7078802303d267155b77e31dfbdf20c6a74238cc912ee129271fb01b93177aba269846ac80e32178efae7d83a12d5d6a75d33a466c3329a363cb28de8ca21501cfee551844ff52ead0242d3d6a32c144655cbe67113feb90d70872ca33263561cf7376bc0bcc4e178498a28a33e6592372757ed3f0de79f7c9c6a49ec7110a7ad1df37759ff4315c4622581fc245df1ed293850e11d6298e7fe948003b9402b673e73c1d8a422393a747a32c6866748f4dfb5e4f620115c24c7e0628d5b5271ba9e85596cf8330b8b66850e63e427ca188e76e1056ff7ceba4ff903752268b35c647ff37b1423648972b54b74c2d9c40e97376c92176fe4e39f77f87a8a460ff1ce304f63cd52f15124a032fb9df6641dae8ac5b123ad6355b55bfb2c8d3c15434866e8d91c6498a368ae8ba14ab5c583e3e6467dbef0e6e78f683b293dcedcae478cb76bfc476a5b9552d885ed94c3e8972a4004711c11a47ded18adf58e55eb020743e0b68144c522e2c611466725a35202da3f1a2454bdf704319e1313cf76b1fa1bad9308d16ad255ce701950ca2111e38a4817b254f69e0f2d2b1011d7c879dedb827b8b740054edc143abf8fb1ae4ec621e53d92ed8f5e4d8fd2ba8090678829de5f0772eb7cd7008223fe2e915abe3df312a8e3f3b5ae43e27c22b9ad0030b2a11d863edd7fd8a3ae5b6c45546d85606dfca5b38efb65b8e07f2635ae243c50551f8a7ac0c873799585a140736040f10b595ff94fd58dca01fb6eeff111cc6393c61159cd696691ca36a28b6011f91666e5ed31a9eb69df663bb05b2c8af8edb12278ed2ba74f0fde448537876194cb2aa8420e3be4cda9dc90382145d1fa0343f32d5d70be8f0e75ff1db27d3b5592b1c7622c16c8608bb25f2532b04716ad825f97ce1ed12d4601ab190412236f14dd986fc7f6199e3ab54c34f17ee76e281d02e3bbf6b49d146d1e4eba8d0c514ed779361f0bc705057957ce0fa670f53a7db260f35fc17063df3b2c75c14e1ffc211e88a3cfd56f7dd82847d9e37ccde265f34515cf9fd3e2aefd932c8db6831e075eb78f43595c553596e0cf205b9fd33c1e1a9296535192503de7a073f78b0043a4b21072693a03712ba71a101af6deffd270bf343dda73450d63ee9eaab1d531e8c1be040575be10ef0abb821f489d142c53f018d877e4e9c5513b95770e50248ccb2ec70932defb3d7f248c99279027f10dc1041bf4f478a1813352716ef481585085ce55130e30fa0ea1ec5f53aac01ed7842490d1eddf4243c1802eb43c173718ee0969bd04c81a452a024fcb3209132db17c1fae231201dac4f2411e7ba8ba7d46faa7325a8d4b112b2cdf3cdd407563381a7a040d4d117d261d1c13325812c9a8be68173a5f23f1e91b61f5585563f239b401c3d8831ea7c5d801cdcd9ee3795387ce131ae000d47345674c442e552c09004ecd0fb5fb5c290b8dd02dfa2975bb00c74b31eb5a2d5fcca3f43b898a331ac0b86181aefd4880bad0b6f6e2eca99dfcb4d128d8b51e365c21226e84201de342151fb2ff8a8c522e1b4c548de520bddc6190060b2f7739d6dae85dc45ea79c1230ed965948337cb1db99c7f96b06abe42c8d098603823412d579e5b579d97d5dd8fbd25acf311da65b31254eb5c817b744082759d2fb5727d250c07caaf7305062115afff2bcec01ef0debe99a3e2e0e493199d2d970caa0ff573710d94a79da943b73c98224a90bb9697dc741412694282e0c11aabae9917368a57bbfdf53eb5eec663a9aec59823f33f7e0326db33c2b20d2b3293242711068548b4bd4acea6174d22c38aa7d74fde7a880dd8369e614f5f3686d726c9feeb3102ccced732b320ec31573a1a94834dc3027d342801496e081bbfca199e140d620f0c110d0daca7b24f605096385d99f82726f8adc5461a3b2e029ba327b0c1f7182b1da39b3d8bc4ebf1913cc014352c6c1f3b930e84cf2da705371c1611a87aaa985da79c967f5728f19850217b41f1ae82a6cbb115f4d86b59cd81b5a6898b6646f4e3c28622fad3131a3cc1c20a95d3e8ac188294d15304ab1818689bcaa24464dee8601d5e71e85e05da9ed46941cbb4c76d7830bd28211aab256f5f61d6a11880a905966656adc85bb0db705b3ae836ab259a31045433ba7fcf075dba257e40d4e1f480ad0aa687b5d1f95649f02ab213c5f63ef055c950c2bcb063500171bcd6d1a1c4651619e2e06ccaadc90e2b07ba90bb744f0eb8b86da9afcb871719a59320e530104c5fe65a28183bf377e2b349f41a28cda01763316f8dc18750573e3d690bc0ce7ef057ccb92cd6952ef4ad542c1269a12d02deac5a23f5f005abe810a40b86d96f397fb519aefe87465a39becca40a855b24cea86e1a709b750269529d8c7eb05a2dad4b10881af89ff52eac4bf276e77b31f375beadac2a0bbf01b94c870a6e4edadcc7acdedbf7b8bc799ea2a357864a31be58678bcd340203583b916029d05c759ff25c8608a5d5a44375e0db76b935095300c2563be15c961af1e184f5e678a74b363be739563e2c84f2820c78a9b28facaa839a88c2a9c61883b6a1147394c97d1d21a92378435135c36a45729e416f2aff9dd52efb88d92a74e24173fcd07da30561008f2cabd69074b6211f98e014b6d735e075c4a2678235309104bed1b5f6eac0c72aab00b502e5c47bf4a6de4b7e8388dcc3dfe2ba61b384307620f9ce20ce8605e6b0bd46b358f574c55a69f1b2f48c4c7947be981686663bf095adbc2a0900e1a341ec352135578022f6b888955770e059c55da92fb09b8703ea794b0f3bef772b9f44ea8ae6b0cfd0477d8bfd4137af4ed606cda0905026ab754f334d5096a5b5a7258349427cb6a3d4d2322371be6d9bfebca80cbbe4263cedbb632b5714b64569bf6283ef1c692a9b845038150421e6ac711c3a8424b7d4d9d106ceebbcf11569c3795ad70c49c276e6ee2d76c3147183ca241dbf50428c9cdc07c782cf78be356cd7edf74c39360566b9c0190a19412d6347cc4f2d738c6a2e148f082864794a8841f1fb45c782a56506b15bdd4b6b485a29e30e3c97ffa872977652043b2a6f186848f85d686684596b0e05a4a4f54d766f207222f130e321f998b536b3ea8fc6280280825a86ea3fd8147592d426588dcff1328cb3114e412fb29e223a54c40447fde4acfd8e788b65ddfc2b2b31a61d4d346b173b07a9b6bcb6075d044ca8ae3706905ab54f481d8ec1bf446bf44429d7087a12a65f2485e891875e931b15a1b57741e6ba816906593eec4c476ea73fe4a72a7ab9c16aac91fc416e83521c261c8af7dd0b4bc76b37b7cfb20708850e4028daa54376d1ab03394ec2407354ba19e387e008ce87b6a9bc00fa4642e333087105247c830d145304584ce0b4e5c6d46f78908fd618e66083f7e5556b928dfceb1307702deae4181ebfb47f63cd1206c192b1f48c21c738a1c6c3d160879319d285b715fda77ce0b1fe89dd1387b00b841506d43eb1d803ee16261f6d1b893e275037cdfba6bd81b33fb409b6ffa1c359ecbdfba7862564e0c25810d5ada8092791b165d7757372247a0058c48f848732e3fe1a39981f64e11f622eec2cb1320477a4a583064972ef9e022aabf624096d34af7f2a5ae0c46c66e36daf723de988af4f01a87470f1a408b175c420a0cf2e789fe395a3c898251fa5525705db4159697f24dfa688a309974fcf55a64a2316d677ee7ab5e7bd7c3465954008fb40f850a3d67a69e94b66e985c2ab4862d05e55f0c8ef2486c9412c4764400a418747bb69ef618721d45183997618f79b47cade8446b23305ad8aec91da691ce00143fffbddf0d31268fb6c7e7b148f08825c771cf35b4cf5912f5d4eef0c1c3297bfe51667485a06fd3121a85b4c40e9f18cd6084ba5c7e098454f714f78d5e68e68680ced6395ccf20477f74e03fc65bf0705827bc3b766ed0a0a0a0635a81e80ded646be26726be7530221dd7f4573b536529db5607178237975cca3e135c742f7386a9f193191fec745c978a9074f3142a1b8b8df70eec072867d3d99137e5167597b82baa4fe623d1377bae15b89be52f0a3369350f237cf9dfcd7ec9acf45a240718c8ef4cf7a3374d941122e7e0f42f8966f7d865d458efef075e9dd53bf05121f301d3c63f1d7f95474b91c84c0f117a20102545901a28d188883c504b808a0d25def07813f40d8ab0e94a1d1f34ca0115873d94550435c9bc0362bd3c87a66441237bfc6f0ff166e02b19d8390e2b004629df16d65bd1af38c3fc4e77507eb67c8dc21680d3a5b1ab9185cc11b061d1acb37ec1c5865e751223eea959d9b83eb22a6509fc15e0bbd90cd64e603ff18e1b30cdfce38cd189b294640bdc8d52d08a618a1ebee893deaba8a661a746e41307e9d90144f58146f831ee253ad14c09ca2d81c08a837a9cc55de43b2e501988b844fc21d9bd9b2e586f32451dfaed796f61186f19191e238dd9c5b946f28dddc3e37cb84f974333ebf549fd3723a5443dcd98940ee423fef9110c768d75d36d0093bed7a8560bd8b5ccf6953dd09ad7e8a1d2e57919e6a55498187cdc4a663d85b02ce4d8a46bd0494193e10d293d6d2a0c97f6bd01ffcb292ba0ac8d7fcac55525ff961ed0c981947c0a98e49d9375b8a79899930773b2b5c99ec9d9f12c939c499f5a2f687cb3b8e34048ef7e3261c95afa786ad946d3b844be5a73904a0074e9b9a256ba17ec35342ee2f4e56581942b81237e84b3c98cccf4dc5be03751642df8e56384b9276c09a3665e48bbf14e21406777cf4b9749eee940eab73ca2662b700a951c19d4fa41ad26ae55bd9f86a28166948562dbfafc4ba140a05db539108ba858fb3ca81e00546d41ddf3d9dfd2009f65df7eaeb166928a6ce3e5f6380d80d1aebf1998b4f1ca31a4c5aaaf1ae619b03b4d2cca9080286f59e41212a1d7db04661a06d67915a5f4a2f4eec2c70cbc1318fde725c002a9da96c462fc95ec1622de2481b8ac744ff953e5cc052c85e6ed8f69edf3a03b0c7e4f0b43b07054cc6026661cb8183fcdce9bc4e168a544e970067ad263f1a460f50b3185dd880ac41eb2e0fc95d7a6d416815756c45939503402bc928bb497fada8d43aa7b76231da22aee84a4cd2d29b99114c79b762e0353618931573365fbb0a31289a42c27b6702f02e0c6ebbb7cb99844cfa2d73b74e204887bea0168d85b333c703cab562c43b9d4d70bd33c7bfb9ad7cdf62b03fb01fa24c517ca6581ae5aa354e6774b68f1abf62f8603c3748d892d3babc64d6694ca9a7759f4db988e2ff61818d1fd409be60b26c9ceaaf5795f4f9cc2f757a6c551af0a0148e2f977f2b3d3dac8322665fe97403832f06ccac3db0f1b238ee6efdb8cbf294b4362108db8c312883612560d00f1d6dd952c33b09401e190616ea58d34fd116effa103d88e97cb03532d8d3abef82b39470d6aabca9823be9435ae801b38ab74b587e072843e71a0b071d0e2f801728aa6ce59ec6240752eff5fa707fdf70c5856fdedb7bd8f85180262e4529a7d9c904e2103c9f0165b2bd2a96db3d4ab8778b30fe5895b09bd64d9843f7951518f198ee088349b123cc352b3c93173e36806b2dc0c08a0afed6bbdba4880ecadb852a5f0ae2677969d9b40630eee20b8c7b039885f63006c27abea6966d1306600010a8e8c9d231c4414bb72055384771ee00084d6158a17938d1e54f84e20e14986b9f9a2cd28348eb0105241b98aa4ea4803b482f5bedaa2f269fae3757071143e6f0d691b659b168eae4705f7e3012abbcd94b0dd4e10699cefa82579824f2e3e0a74bc77a20729ee5af00a0913c0894d5bf2b4a88731c72147563c3f1f7e4c243658236dec0656261270693d6dfe536d4bd9213928e5a21b96f8e73b1d88497a3e42f4db823382c24a5c7a80609a3ea61687308d380069e4826ad0cc22293252e0b15c0764f17c6defd54d02e0c099cd40fc23201a4130ce06af1066d12be4b31358039ef098b04fa08cb68b1d48091156d12cd29d3303030db042f9a1b169d680769d7365eac54ba9b11da1267db81e8a448e1bec4cd0289b456edccb1b3d87bceac950f1f7ce6c4972d27b1c3173258eae36ee6e83509ecc50e7783c620258024e847e38213acaba32ada1a68cf8be586abdfa53ffa66feb851a3d9e2480e2b82e48f679ab298844138442efec3862ea5b0bcad4f3076d527d01082e97dfa1504a4195269577b1fd20a69f4300479e6180b49b742a79ad17a39cdb2e417622f0234d9429af70839c6af87e0df5aa5f6ebf732991ba88936cb08c4ef41f9a0c968516f5e667b06e62c0edd1842194fd4a57b68a5fc1e6e5a2f92ab500882eb019a199763cd4ad6df21da8a463a4d315afd4180574c9e1f01a79c9aff3fcaf065c2ced236c1eae1d0e516401145ed67a77f426b470bf5a41c5dc625c873f843237e591e26b2a6b559290bb148679c515a9c84d9b767913d383f1ccc4d21c42bd30582f7664f2ca3e8e66d5728851988feaea47c5a0293c21e3a1229849142876224dc511e767bd8d2ed98dc2951769f71f33458c84814ae208392d17a20610d67ce45a280929fb271b778f904650af409d60f8c477f3351223d5b1c46ed962c7a162e6c883e8a30e444e8af7e7ec549840be1a261c2258f2182ecabfe20f1762202ba8c8968a09c8ace18d0185f09019f70f0b280ff951bc73ef2a722ca2ff9b0e0e9d9b2648a34b1cf9164ce88cbd48d01925c7836a45262ededd139c34da6437eb49e28187e5dedc98b78d7e275bcc7cd7c09389772b7c19b1909ffd42c0fb66afd512f0b9443f7e88885161b80bd90a6bb70d4a91537917572c8ec2c24602c67c1ed91f6f2c70a06986c3fad2793635e3bac9cf68dc33a98b398ae6ab317eb268be519ec0a02892f3de92640f4ce5b445003abc58b12e1c063937057a9211434a324e57979a250e7709c0df6509871d626b24de5a6389e318d1a88af22075ffc200b38ab0f1118174cc51a3857ad062dc1b4c83139290f5d2898f1f235c124bb893c22da63ed4e63fd2327c884105f86d0352b7fa0b6e503f6472b496c3441dee7b8c107b8656fb894e98a7acdee48b72edfbc705b020c263c9ac6fa1c6fbed260c00df46cf09bf0ca4ee5e7fe8ea82b12922cc3e9e983226c3051c0e316bced25bd8c4b4cd8d0ae7e497d17b545dbe0d2c7b2a172d73891e887f3dee642e31c9aeda2e9a5b9d4967ec104ee68e4656f0f710db78c82c2f8ec35a0d593bad15e05d324dd2b71d7c0cf5181142ce514560b94c8ece153fe2a7f0db433a8b89ea0634f115aeabab6759b146d32b7cce1274d13c0a66d38e6ba30c1786717d1070178372a8b338652ff7a4075da351358af0292fd15b04c40f82b48432dc7f5a66e431c517c5ab4c4637d9dc0493e799f434a7e0af2e2e75cacaec3e1d3309e1a0cf1426d74374dc39674ffdfdda70edb90cb8c6a2470b2239a80042512e2b19a70f31614d49fdf1e5114671b0a5e0139c1dec113a0c3e6ece59265b14390071b4b011065561b2a5c9315fd478f1c626b49a2611a94838b9ef503808d90c80337b67243a50178ca70a709f030dd35a5c58481cccddb8cde03ee4a82d204c27c0d588cb57e5fb646282572b15ff071aacc25d25e040036a780ab4622e9f6a4d99a6f9814dddeb504005e6f76b04734a64df5eb7382ec5a64175c1e3327fe2a6f5575906f94fc12416511730dea1d9ded0ac0df9476d80e3a70e1ca8a1d52db7a03d12b8fb403c904698906295bced3be2378d82aad64e7a4f77059fba82b1671840dfeb969bc571e60d306b03f9003e6e229f8b2933edc4d53868722c9e7e8bfb04ad04198872052ea4e0ed619a35c2cd7d3051b91e04d0fca60bbc709dc396ccfdc080cc0c7fa3fa8bfaa949ec2a22fbff08d11820bca49378e7d29825dfd1f30d2bc09ed8fc49aa1ff1daecc448f46919d1901151dc4a786071501cf12970223b67c9dd3c1f5e507b648d026d989eb44cca092238123c5a67d2a2ace788279ab54689496a84e70a03c95459cf9482e73bd9f06ba5ec7eccd93ad1ce3920f5ee25f6ec161a120d58b950a7df3e2a96131b4429c353951e715abddc5c81bc6483499400e56cd809eaea73567748b16c76acca53da77c8a9712301a6f2def4c9544bc15d4698e37ec7b950856b8898f3748067a87f72fe34cefb0e6bd880f6d3c7bb8aad40905f085e5b68ec9e2b9b9eba689ed5ed4a4929af0f606d41ef11a9ef35a36c8c8c1f1b2b109e0c2749cef9d2d0206a549ca389d3f61a553f373002e634a371d44ce4e44edd488a6232f0a1f81c33f468470f8a323e9c895d135436bc501e11790223191bdd7c52d584e31c44dae120515a0e61990a29aa92b7d8868d41a8d239401293ad6795b0fa77dd45f4ba6744a2fdca535468413172b9500ff0a013ef38d84abffef2101b332acae7ee1009074d001f0a92c97002d9a2315233c0a400bcd71fbaa1aa5f24e0df1388be26cdaa3f10c6f2dbad9e7a6fd1e7cc0a71c6fac84dc83251cc8968c52dd52295c614f6203cd12491fc7d116c3ba97ca765e8945c587256376b54d9ddcd7a912ce22431912a32c47f64911788b79fc45ee20597abd127bae2b4e8240b620afe36b6ce41693d63e4755571486642a2beab05f0c93392d3f6e19eebeaf8c344c423588d53393f5e0f785044a7e24d0af9c040c945f1452cd2e259a345572d4eabd738f03e7098c7d93c6deb50ed78632d610f318c4312398e5ea452afdff51ff7123db33859631b80a15636bb06d8ec7081c726ed49d4b37907b00d505e40219dab3ef6b4ef1ca4c35fc58b43923c44f162dbbd3a96fc58152d657541235d26a2584e305f9fb55cb256c766367810083d5169e4b3c39fd09be674adb1025d6da3658f1f84df823ad5d051c8258b4fce634d213d05831537985795d287ffd3c530ebfc4360ffd76d78e7fd5ad0c6e8c4e3b818ccda6cc080e631b1cadb5f289e1698558f9044947a20a63cba4dc260328d44e287ca6c1a807b4c5f0c655287b241a9555d1ce7f3b0fe845c53f55e27b62d68609f8ec7b21e8a1aa449d51af617ebe1e1c84e09c757b8784a111413b7712cc23893baebb7e813fad104823464431827b416a622fb9d495973cc3bf47c762cc60b02fedb69a5bce04cfbc7f9fee9ebe15ea482db8665a8f338775fa96da7327a41a69b7f8c63c3694db0b897b8c97fedf40242410788c784e7404591897d3fa11a941049a97615200afaf69adb02cf8037ec18fb5692b27ee75b58a84b0202dec69ca725bd9658dbb09fd6318ffbb1580625fddbd8f96903b3ceeba3ed75851d83825def415de683b84e676046d2d06d96ac4a7ee4d4035183ec5cf3a956b44551089f407a49a4eed8a9eb35a63216c53142d37b6e23b89f2ffa95adf5aea72130e4416de0153745f2140b1c139d8064c3976639b4317057a241e640950543782953cc29604357a1f04eb495fc3c5c982bac67ecf2c67f2c5fea65298970f7567ee91af6d5869152a68c6cce35304ac9c191d2fc756f15582c2df19ab54641d3b92cb2c58199c6786451b912890756cac1447bc744b73f362b1d0d5787442bd1c8a2a86f4fe45871aa118d601ae41b81cf4a022a39e9a93cb5878ed813670b23cc00c71ad2b8699c6e2001f019f102acf7bc502e24bb7372e125a93b628db440a5a2290553a48d2251c60f32d559ffd1aa4a493443fdc5eb7f6c97ca7b94a6748a861476d3cdc1ca248803827e76159d9274721f6dad43a8a85a039d5f9e0c73f08f816314c70ab3c08c9d8f1d5d1dbec9a9b09349c6e5cd038be81aff730f17f5a9d956d96dd546d491c2b938cc8287655e91c149f8219e1d2aaf6fd9486fda517e09ddad02b455fdcc6c629d56b95ac92eabcc05a95def46f196f3e53f183f57c03c2325b18a05c576ff604d06a64353ad3c44ea57892e0947a2577477291bee52362e714f1482888c565b9c181113efd769b84f2d329acdcd6571528f9da1d7f64316eaf818f893b979ac48911930966411fb91483a02f7152e4e5cf3d3d2080a0855bc270ce60c0295275c320431287721e7baf886ce8e46788c3b2b7b985c163a0382f331f79fbdf4e0d0c6231c106b1d8f2caa9fa504f13e51c81a5ae2b7c3829728400f720b205d821bc72430a36c2e7eb8e12e5dc9de590d93a27a455271460361ee0917289e4e8898054c94abf0652708a6a10eed2f5c5465023285245f8ccd53b860c6b381d693873af5ed7137f804ec216183c41b60901d3f4cddc14794c04898aacc7e919eafc835f21582fb2561b7c620b0a2331a39f14b97e4d50ea3e58759529ea58b18b8c85bbf555d1a58e73f38f0fd9536df6ae4f61ae45843f6f0a75ad7c6c5a1db9cb51ab437cf70993e7188b43f7a4c87510162331c853db06c0a5780436854ae9781ac320e283af156db4ce175400bd7583c06e7b758bb27d18c60419964797c60687a02e071b9ea4da793a4ee414061fd5f5ce83e00f4d02ac4ef6f2f1aa56f8bd2f76834f48d6b50cac64a1b1e879a9c5e5afb8772aeb91b87279ab550c57b79ec9e568cc045baac0b681fe21a92f995fa4a405fe3e609b23a671f171008dab10da2fac12c6979b34c60cd5d6520be8f96e0f93c2db460881a05ecd9fd4901a47fa220e87520ffe1ef7e8020d7228844a5b440247590378057b0d83c5328845d06dd0778c49df45fbdc18ad74105ab199d2bf764c9b5261473a393748f43c49951e922fc96976d20f95b9d4e9a1593d98da1a651d22421f8006aa55ff1c1de10cb91f92e1987eb69113c53dd5faea794b92c9bafb35f68743389d3834e1257f7f917bcd525547d85cfc4c6973bb62180699d602ae8c9674c3b88337a2c0a6439e1d9ac64b19bca033defd87c22f35f5292b659c41884a66b230f676bd7b6f05f7db83e98e4d2709ea4f6543460e817d24325688c8f67425bef63918b8cc2f8ee1a6dfc302c0ccebae53af71f52313f4191f2321d5d025506f5f6ea951410f3093cd94b12d5379dd5400c1c995a8a3a81d3d0517490242666d266f097e06c712e4db0a06f216e8e734d450895db3a1e37595cd3ef525b6840cf8b588068c2030c8e4612ed20462f5bbb18e049bf62e16e923edaac2dbe32ead92cd15eb852529df66b3e747d7382ddeb74b34b8f674e2f6baec688c76b45a014280152112ce3d49bdacddcef8a5640d19270bb7a2ea3b4aa8fd5853ecbdb61751a7be630a3434eb8ee24e14c7c28de1a6a96f8795e46ea025f2d04286dc9843115972fbfe411f553a80febaa26bbc051503fb2bcc2eac763b9ca513d80360d55da05fc49ec2b5e2257b2258d0ee3014d2fdf43829aeab86bbd1e05b593b266bc46a51a72762004226ae92b33a439c14119a7a91c533a235253a181ff419502e5ee9dcf3dd7e8a9bad20693073599fae02da21d9efd9b5986be20a01d052cbd6e9bf1d3fa034b95f61ae2be05ed5e437d196f8b28aca0c9cbe1b8242049d2bdee901258780ff3a54a46fa26ddcda2269f0f0e0a278632875021145da3904e21cb3bd2e5d4b4a05d58026b6efc6b86d978afff4b1b7c911b88dbc1c43a2aa0ea96be926bddf684287f71d1dd1b163e42bf3b4e50533e08f493d47d8cdfc0663020d4463b7c6b9f2e0cc3b2ff2ea16ef7fcff21f880588c4901daf4c4e34922b8d36fca6c4ae8af4d84d7e4333ed683d86ece76c8ef482de44bb11e89f980b830a857a8825e566a54d5c693a5fa655597de93d5a8fe6b1aed7812105c5bbf659c7991d898d4f39127d8bac6879e9a7ba34411456d12af1d2597f965e31fc092068e6c13584eb94978edd992d467e777fb17cbcd338d03579011405d24ce6432fdda3d7e65691d2b6c2d2899c62da465ab49a49b32a807b3f73c14e6c7968a1ac8b9b08ba77004b5d6739f9c83fadae6fc546b09dfc860c62698b7de819933bcce4e65202ab4f131424accef94701e22a42d4d710f21f99455293f9805067dd77b6a74d309a4c18190101fd58c427bf81ac9b950604410cf7500ec710c01542ede1bee5bff4cdcc97eabd867ea3224e2225eb12d399d4069a3dca583c42ef9157eaf7938879390bf0e2bc93e570059b46688a1e1b7703081981f0f119c7aa4cd89d287f4c700161d199e11126e1bb3c47fd14784df8453b5e63453b0298294af434c5289dd251565c870832f34216f53a31c8b5549fc5b807935e4d310a97308ea8419e2941a66f483dd56cc146afcd2400a1d710a1b7d27861a43c45e90914aeb665b3761e82c7aac9af98e7aae9fee0d84cb1630359ff2b8d7bdf6030b5b40c50b53f313a1920586e9b756c4a1307d99851d00ceb104e8d16c54a4e87e01a7bba15ebc60e25371b5f8c4654758c18f862de5bfe6f12d770df198e1283240650f374b5294b709fdee51c569951836c4ad9b77a35aeb2460f60aed729aab9a4c505eb404f473315c368be3e21dc5c5a0439c16032bb52691712f07d3323cbadb41863241e66f25a21f0576eb80f93644d60a0f0125762614332ac98e5f6d37880a94d763a3699222ddb36561cb5ac9dcfabf1f6b635be302ca14dcb06af78792ef63141993c6ca965f813ee9abf7317131cee4ad731c888441e2d5267430efcf6d7a94e117a0132bbc449b3afbc960e776671fb13c28a133506a4d491482cd64811fd085b0c4d4d294f1d4aeb990ec5917b7f16eeb7298f9c2e924f903c70b249ecb370cdbb316d45d15d1b21bc968e982c84309f295fda480642fddbb3cce8f1ba90cf86ba4ead60d30b2d2f0974f0d098feef3fe491bde0892b16356ee62041e53bfa97f75708b5d3a5a3112cb0b81f8f9a420c44963dbba94d2f7c1843462bf208ff5080caeec26f4654048e9cb54983c70fa435adbc596510701ac6cc9526750e52c4160b78da81411d3e083e9118f47cb200c451987223577642874872d229907e7b09a75b2d6b39dc378c2b94735eb5c4def66dcb43c8e4dd32b5c099c2f49a243c1b3e881ba7394bd784dde2ea27886974b2c5348a4809de288bf4c7f4338ee589ec4cd7f54be60455b50d5778455fe597040578bdd25828b3e684dde47b473315186103c134859ed260084c102ae5bf2a713d0f46ab4ddd6a12749d280b9cdab05361c139e7f3e4c163c366a21557a12a0a4b261bb7d5de6fd9b49628c6b5d295a2d0cb6e32dd458c3720470ada95cc8c307be32406775b58a1817eab0557551f92c3e842a76f098e9ada6c8bf3ce90724b388f4e9c797cff3c10422cc0bd81f73db38027d0c89bdc5e22f703e5110f453913873616aa98ae61ae26f593e419e1a25a588919011064be2ee5dc45674ce0905244b330187a2cf38598b779d45199ede3fe8c3d6f6c758f1be16780be569986923f6166e6af2a96a0ac8ddcfb80924b20e458b3589470ea45f34c7f47c99322e0a419552f2f98254e8a0fd075e50661001a15e43f8e625c67b6cbee561bf8a652773ae1fcf290bb7e278ef816e8f2de4eb9d1c10f3c740c8e23c57bd7222adab28e17aed1ecf1c265bff2646bd8211bef58807b7524efcd69895ec39adb39995be8a72c90613152137f36b8ba554ef6c1ad0b098801853d541be2d497dfbe3e38e5495b7ce5ae6d09703c1e6107944b3e4000ceb6de3ca5e18872a1aa6e92c6c5e8da104111f22d5ad41fc0a9dd4f0e15036e315d29c0305db652602d9443468592110a832a850389c68d1e3c1c1d3f8d0720d6e3c94421394f1af15e087c2fd7d9143c4d5c048a8241d6e650f340713cdd49f569ffa98edd2abfc223c08354f2f48d8d6a651ffc89e4042bb89942f59951aa9653d0d294d67ef467471add6f8e61cf0643e1c4af4e2bd1bdac11da9b27a102a4e89001b9143ce80e9cbfee8a868f33c948d8a46ca1bc01c246061a16faf6ac3d28ce49f407f252933171a598bda958562cbac75c8148995146f21e9895377c763fc4f5f47448a52ffd1de1c750299cdef603e69ef75e0e86050e3fab275f1f7b88a71f36483226683a81d030d4d804aa8c3b6c4e68604a13e73524ff40d9e64e8fee1ed782d2080335862b7c963fd412110a40fa9b75e5c622cddd773695e8d7c735d0e31d6b42fc6393fdf1b6ac74b45b83362a685b18c77ca4bc1e0f974b15dc03afbb92e541f33ea292ee0314962b49629489684f1c388c0976844c19bfb4d53eef3242d23e2ce8765aa0e1c8c10d7a829c82c42d33a705a4518b772b0ea2f1b66a30729276585067e2ac54346951fd782a9ee781f8dae22d4106188d617faf0116eb3ec0d1109d8f39c5d99f41cabaf847f3e88255ebfe083e990f846082ac0a26db2f12c9cc98a4304b3dfcbdfa6c1dd56711d158781c48120255a1f9a2b68d6154db28ce6a8dd6184718367f2e88cdbcfaeb496a02b21746149dc245eb54ea675457d6286a656fa1aa67ec3362e0a7fe45e15f3e82b61baca2d159e2c19df91dcdc1b6af33e3ab62cc919e10e1556af11bf72a2bbd98c434fd0fd426dfbb5d8840ed6b813094c475990740b880fdb78cea32e9ba04d4dff882be94f73f67d22489a46fb22b8700e8936cdbd065ddc8df9acfdad484717ec20d015b53270b8e0ceca80660ba4218109b1bdb2bec10d5618bb278a7bcb00cf89aad715c5d0367f4f24add40edab548d64804cb388a3735189702cf2f99f1f3359a3c5691f2984f86b9d5533159c2218ee954b02ff159a9d00735f18140874007e36ffa79a1d5e8020c6c246318de7ff842987147697175902050bfe71b40384f693ce31d87e17f0f3cbf46c974eb63eb81c4c2484475b029a194917a0bace1d88282848595ddd56f90caa91ae6daac9a7548f39a6a1372a0a3112c70ad3e32e886834438a56eb9d33a6d716e7af290789f66e0e425708456f7ab81feca41d4fa8e4b603b95175c30b54ffa1c2c139d417b8e79031cec161cc5f3dbf3bcca9beb1c5c43abe98bc941840ce48008a48462b01070b607480464a172c538419183595f056034a6f4b1210ad3307652393a004412ef44cb1a9f0d0d8578d7a848facd693ae528f82f38cda9f32886b18e502eeac036895a263b06fa374fc22d8165c958df6b1901adf13031f2f8fb24ee637f8aed3c12c980ce4fa43bd45fbc76e58dcb9c9be91db8ac4ae0f9da2fdfc052b309ea3c08cb8c147320f44563de3a6ce47560ebe1f35e5e0dbd562e7edbf46b41f0c26faac1c58e756de2638b4cfc421ab01502b4ed746a8236f6e28fbfc9f38488ddde1b63a37544bd692872fe8d796ed391d343ee822bc844211d7205f58eb5bd17072abe8aa9924887c3982308399cba1f6c14e8d85cb0c0fcd823010ed683fa8c9b64144c537cf715678c2b4a9018ee666d571ff603d9337583e2dbec212dd87ae9cfdb99cded26d7918019956e72ae3b34eea9cb609e280acb2cc3a8fe71848742a44d78bdf5e2f56a159cf7e4e1ba4a43a5b8e5dcf201f8f643625b0d6200260776d3a3a67ecfd412f54b50e87b29f1c9c9ec4afc077f095972d9896fae93ba61a5be99edb505d9148e0fd7cdddd0c7a4073b65ad219a25a381b364a52080af893828eec96804fc8085f11f5ae9666d57dbc78acb7616b9beb6c916d6b83c047aaa4b98a78468d96dc4aa699be85c37b99527ae2a608292b8a7dd05c82a1742a6109fed843ce3a6e93cfa72268a02b0ab09e6ea9f0faf94b2ede569a69c3babd1a3512245c33ae73c6d0a32f4d1b730483b233410625f561a1fe0b39b20f17de5b500af494d38f212bb873a2fbd9fa428760f0f48609163b9028c9627e7f6f6319b08875a820f0130f6dca063b80c71199416dc019de61499381aa1af9a1074b727ee83d8b3ae9abfbcd865f77c0db9533416df02738fb6d90130e9e3075fc73ba5580aa2a99660f2275b706e0d31a9f3435e7e6fd6343e2777a468fe5e69fda61f409f7af30833d4168f143f81b30e27266133301d9023c5285065997aeeb0848e7a55f030b28d0694940acf0f9a4f138a1871e5267b1f8fce79dc2f2e24d26707dc820afe70da1776084cadf9f8e28dc9eaa9db6338dd1a65f9ea15fda30036cd60d993432ffab97c574bb26b4ef8826e078f4ac6d531c40365e418f6fa48db3ff711082b0cc3e03dd0fcebda5461f46752b88657cc3fb81dc08f7068440c4ece644d09689e9e46654118d256dd9cb0dff128ecbbb8a39c84f1ade40095e0978bb00e0f087f5abe4c841b21ced7813dc3e34079d6ef3959196024277f16c19442b61f837ac278acb2a08287c3c0b4ab145bc2c6c2da2c36dc006773159335b3fd0992ed70ef0de153787f69a6d8d66fc2293dc29b927f1065e7c0ef08b0e3d49816a61d8f29e23a4c8951820c9f715bae449de5e12047caf3fb2a05af46904ba259596671956cb70ab045a278d6aa708d39c4c2395ac00bdac1bd68b8b78ee4464c47bf0c6edfc6a091943ee0ba15b3d36f630bbbd1fbc7d38fa1dfafa32e4be606164611815abbaba0830c7ca41819a9f275d159441a6fabee72a0216328e8031796b4d2e101d910b6164c9e3a84431569d28db0d6d420365b8b3ecf26fb66e1f1cad96b55eb0e692027494e5bdd9aeb13b33f49e264a191e4b076a70476c7bd8656d96c10dc7357ed81be15508dd109c00d31230987f3bfb40008241e340c9c24e35a3f0bc3a814dba62edf6d1494d9a1035e21ade99ae641e9b6fccccb9a0f9a397bede62df0596347f842e505566583a85402b8910630e22a742ff21974bb8a85916840aafd2407af57239120ceb4674c88679c6ccfb11c9493df92a0815c89bf0f1d1a5e31087913df04dea912436187aa888445a3197a81284447ddf14e8831aed7f31385303373d03b5a65c18569080840557a5f26df9fb8816543d450c2c2c12578246a2e1873d261baa5d65c76044510bf69d09597e791b4d4373e95edc9f09d47f54df0a06f7d0c55ac2054835c8551a813db388d7ec2c685ac128c98071e234b4890624a07b7d1c7a535ee440b50c5e2a536dd9ab6f1229a7aa8fc76f0085f9807953c77dda8f4c7daee0e7edb582645efeea892d96c2689fe0f263cf4d68edefeb2a5f091498aab4246d3076d5ef3655a061fdf941582eed74c6759880b8bc5988eefc2804136eb0b3dc653b07e924d6483f24ebd90892e98a8580c2c4bb0546a913d30c4ed0dd5c29aacfa92076c64af7b1142a9fdebffa860d47498f95e8a3f3c761ac5aa34593793f942fea18218cc15a2159ca73aaf661d73b8345fed3a61e9c32bf1d9906258c3e6329e97b6ac4b9f6bc11c94b82d2457a54eb23aa00c4ddc60c37473f3d09905520b48e42155c60df470b433c7c218f8ed12ee6a90aa33314171ed23c63feadc38322aa31eaa585e7abcc11d5836313bd3c0d03dbaca51461b8d50c04740eddc02002e61ed43e926bc8384317dbbb6b02d4916c732d207e1dc1274db08524ea8dba73cbe6a11a1e19adb67b91c7c5361b2935e126cab424c350c853b665cf66416cebbadfe0bdb1ae5e427086e048d09b999228e2f9a149a92b8ad80053cecce66ab5063cf7a40401ec15ea1590dd62b829adf08f7af1c28fdb62395cc6d01ece858f9da2e987dfbfeab88e4efaf04f17243e9eb3b0455c0f10ff419661073d275401679f41bba181fc2f6853521937cea92c71ce9d5b6ca7f869ddd9eb2c609349e011432010fefce0cc66c5ea6669bdf9fac71536b7f1e07d7d012aedde00d189b5fcccfb10b0a1af6b1d8bd07588849f63ac04377976c69d0b6d599c19fda522d61c6f952db37db06ae142989c1300600f63011a8f8c398152638bb31bdc12ab1b54906d92f0fc25f25ac60bf5e2197eee9334ca73fa3ec23021c4f8b3d89e98f920393fa61406b7e2e0347907f8c88934624639823c6275f9a11252c92bdd12503a0b2cd627fe65fd7c3c7801c355b310c232196c8f8d546ae30629e0f864a1da948ce1055d8f5c40c04d942a6859220c404e59b9b43b051d083b4ee2cd8f5d7dab678bafb930fe9910684e8fc5b57aa290847bba30db7ddce48372a10b46bd283ea5690a0aa49738e81bc370322b73987290e1a1712f2f21ea0d86f5b125ad6025b88407caa570ba4d21508e33af309107cb8b7034e15738ed3ab6a503614abc2cb88ef6508063ec4abc935cfe77b8e57e4499566d6074c94463b55124d1eddca973d359d577afb0fea17facd1428a42d460d08774525828bdb9cd4d27de4a6927d3fa1e2ea15361d525de2cb43c13714d5a1422cdb24ff481c9fc94010a8ee4cd37831ca15bfc296a3d4ce336b849b70bb97d308f0319de7a30571f90d60f7d4020e113043701fc057396764cef9c828add6b7d3df612c0c4b653aa4b974e47e9f63380684cfe126546a324abff437e384cd9f74a37a50bf9a5e088df8248cd5ed85666fb74332b301cfbf9aa2bca5dbbe4ec16d2551eb226b8ed8f374b97b379839f08e84dc847e47b105cc927de222b09f132bc89b8fededa444a828f6855d66b6f2250e5fe6ee95bb0d1dd3a5b31896bc67bc98de98b48f81826605790dbcebc56c5c10eab0ca9850729bea03d4b926a760f4ef41579eb50f39d75cca39edccb16bb927b9e33bf2309fef0e2d31f109bb95814747beb9b2a152325616c43432654834c97ed2b6e446f10259e2a3dda5308c23e2c095593d6a0b4ea619d6ad5c5d1a854a36fe8f1229a6b940a7a875de6f7d2f1014d5dc2177afd8b7a8b41b4f1ec9179029c881d03403b23542df429124310ab92b89f9192b2c87f8025f6a213c6c8b33345e6c3466ff63e82fb8fcacc85b87457b17dc773e2c6db39ffdb9e78cdc1cc0ab1e1e079b1d63564a04ec5e115332eb58179dbd4d738c21b8a03687e5d42f094423160fc18e8dc3dd70a463c628f8e3d0f00890a17fa0eaf94320bceff7c656fb642fffb4e1e04dc42103e840dd12b1d22f4acb68568792bebe7d388ca9f58cfff4eca96e83da9603d94fd4ac82bc954b70ed07879a44743bd6c60e14a1b4fb80a40c2170be49079bad5bd8dcffe554a0db56d41d1de0e092c354f85dbeb226d2c1bf39a5cd73e0f333ea9c25900c0aed5cf184b347b2bdc344689c793ed5a574097b35403c190b23040c441393cd486c8629f8138be467543849abac581fd6b36e876929d779e726feaa50932d4ec433b5bc45174c24804a8914d95c0ea5b68dfb29e4c3641514a753d4b8f5dcc539447dc8d6c43f2a234411f5bb0525007154d8fdb8a473a27ea839abfc0e6931b4d662a2771078c8ce914b4cf405dabc74af4430ba4325d964c74b0994c248f045965cc7f27cb49979a5f60b20cc43712e97c9a6e77a912860678ec70b32709ada5d09c91d13c19b12377248e368f0b138d92ec6ce7604d0b1b5f01b45286ca3d974d8392fb686a28cd11602ddfa67b47f2ba5cd89f79617a8e3ea39922dc92e8c21da2f747c4e02c97c5f60e9b61a1d18c2e28c692eea7c59f6b0fbce85a1fada95e9f4145d1ac01e5c81a4c56cdf5201f2bd2ed2227b9d2b25be60e377f43e2f5dcd11947fc3dc853d511b59cf1a07fb9c047923528331b81c500378bef20e8f53d87828d8ca6c125ee1bb3287e10bb42285433194cad5bd64e21c9cdac302ae70953d8a0fb31135b049254d0b71144d7d9baa1ca0e544a2d9244f10344e0180d4de190abc54d3da0ed8b612d4f306034dd167e633524bd0d6d1d2f227689f7c220509d5cc8bcf69dfa3e06ffac33942ebd76d09427fbfa1afe082232d304a595a4173c54c4efe8eb7dd985f07bfd669c1530f2d71aac25a9dac001e264176af14ade816e58d8c2e3fb956373d0e3679189a283074cb06f91628861cac9de74db8415a803daec2786b0827240cffb1478d779bd03552b92dc3ba5708b8400a9986ff09cc236e9707817064660406d4eeaf79df1016dd7c23361b91a9ebb168ab26ac282d78f050e605699d3c5d620863541996144d2a76052a4c41d7fc570f08cc5a41c62df29365801203f721599f54a88cd17a2bac5f9a25be4bdf94212d49ab137aa34863cb2485c761b2145884b4009317263bc85d15c22fc3019240b52102e734a3956c3dcb485f2f951807e9bddca20ddc8c6c08d60c09bb980f45e1cf490c65c3a784e55328069ea35023ad2e350a63a831c4356a3e85407e27ca40b6fe4417ece23bf8a1ca04e0450d72a87f426229a7186cdb8700e632387d14bf3760ad02358d9140b905247cd09ea088486f9b8ba412a3e9d3a844710cda8a3a32287806a92e2d6cd37bdcd91151fbe0211a0ac974b6e22c71ce18507ac1255b6082a53d969a798868ce37e2230a01d021ab81516621522f0d2fc083939fe9d42346ee2c802322ac32e048ed13531234d210d87a7231a594483a38361b30801927fe90af357badb3618297980fe6436dd4a0ab1181e05e3e9d6cb46311b11d761f7254d619e1f2854be04808c164249299a6a5f389ce25388958730dcc790b6b1bc3d3ff2ffb3dd619ad7eee3297836a063f2c366bc763559cd9b3aa0d92a8ea686e6ee3c08a4b68f668e1cc49f22b70baba754ca08c036ed3eef23f1b95c2220a9e16423be4b435b64846eb173f4a46921d3909598818b62699a1f8f9e027d5536b699a589eb755d6e10fc8d5617efd733542016184e4285bef7eba36022ed8deca38a66fc828a91b96fd007181de80e060697a90c571a78ab79828f12a9047d504c61f93e7b5355bb0cf827f06c6a1415b669081d6db00df329de9984f2729eb58860c128d310a087fa50e9f572e914b0399d4922ca8f1cd12f43be7ee414419f0590d30404aaee3ec522e6aa547fab25b717b17609cfcc7a45f117b04f61d3f5190297bfbaa29956d8ae09d4ef28ff8a054b778066255e231d236ec6ee4a79b41c63e9b8a645919742b259b608daacbefe287ed1a006d1f79585319f45aa37f4ef80cb5ccc8655b28e6f6ccb386e4401d2bb6278fdb1e64ca83b09da1af5fcd679b53b1ce1852c1dd66c6db81f9d186c1f32d04bcd431245bdaf5c6a4b0801dd83112defde6be5f043637533be372e836deeff73f36b9eaf70c845116209213877f5da31716d356f2174f6bd362e1afd1de1a19246c2c55426753a92938505f3025fc869995fd537b9b70acd667efd5d0d4b91097223d6f24a6d077e91a2b1164373c9ea81b366d8e6d8602b8e68f55a1ab24b477a55c41454b9534224862423637e1d1c5e2c297550260e8c8176a908ff14ac7ca1911839057d83d2144494d085e95364956e5b751f4187ca231dad721fe47bf84c3b06447e5458dcada56604b8b454fdb83526de79c0dc6efbc5d0fbe29cda70a7394584c431a71bccc645baa667d092e1384987090d78d86d3ea0f53e31769aa2d6e62bb7820bb56af403134085007e1244be44d81566762ad602b2fa62b4921ff81c05dd21118a2835eae6890580dea4140ca0f7c5cca768b1e2428ec8e556965979cd590edaa49fb49853af223d0c5804453861ccbc29c5dbfbdaed50d2be67ca2d15d0cf32a910e9834055583b9472fb62125d55755840cf2a3f20d448c19476a96102fab9844c81876989095ce9c4b050ce498206beba47d9ef63bed3c6142bf0feffab403dfe1b6925fbe7e3e3c6c93a923cbcd8a52d7f0e1bba5823332279e66fab528498880015224ed74f758d633d125ee805154719c85cc6a0f1ae89e8f7dc14f7da88aa347fa485568b57fcf44d2b0d2d09ee57c9f0e652de1f0084dfb83613a32c0fc5b10da77c7d6eb2d08a2535b808ce0fd40534ff66804c2964bdea41ff5543d7644c01488dcbcad01d84fb98e10fece4f1fce80ff5113055c5039bb82034b721d73805d834a953e1ba2a0b1d3a7b5b2f976ac1e7f7ee6d86cf8b5ae86f14580e9832d0c7127b5128557b885449d7d89e91e7de1c09168c105ce49d22443dd9a318f4296784f8b466f54e1098b88e995391cc32d42a6660b0c03ff39aa2fe32b85503ee92239653e404a061c18141c2e735e1404963086f850608189c6757d3f54f86b5b5e056864671e8527442e0e875a09b4e5035251f11ac7e854be375d5f3834791ff7b62f533800b707f2191640aeac57c5275daa8ae4dca495b25e0aa919eacd138d7dd8ba52fcce76852c255ae85537c17cdf90214f4628efb0c054c1beeca595aa6b42109c904a662ea5952471d9b22be7c7250f7b5bc67c9f4c806e9a12ea907215951890c0fa7e8c417f25ac76eca5eb91ba6d19cac8e844820dd19e55db035f818dae87588fe51fc5580ace9d7fa99e86db977f6aa8d495daa94605bf9dbab7b4b7fc3eaec1c088e4300c947923482a53592505a5ac753c92403a3f520c15012e864342be60e66071e43c0c10fb3b9362945d596a5291ce1bc92f92e0a909f7c0f037a4f92755c30503a4a738be37dac1d1daf5ba2bed79a4a76d5829d265dc5f5967f9dbc9c39c9f88529ae4d1727a0d022a432ef42e8e8e63ebe877931dc793af298bcca2e2fb035a3a311b9ea370e8d50a3f79963c01e11b1b6b5d6464b4b4b9b04f50609075807311b11ea2db69b0133203cbb4acd7e8ec7733722d4615dc5fe9c9cbfd70ab137161abb72106c6b2b8d8d7fc41dd9e37b328c6634e4d4cf70379ede8af0fc78f8d12582e263452cbf41c2df208364144f7362e3c11d0b3c872139ab1fedefd9359c3d270ec21d98e79d79e66db5da71734e0bc4e0e238c9dee4620f9f237170205c499c599b37bbea5a9f7da0679c70b3964e8c62afa50b1d7fd94a818cb8d0f33e8cbc0cd2377fe423d0c592c4d9cb109fac798b392fb8aac752bdbf8ed836c5a87b19e6857fea32a4ece9b29db4a68b73c723f597b72239179f40743ff196537ad57a64a73928067165a4c479c7a8997cafb53735326c68661cf5d4bd3603cb57ad47ca5a6b6760d7292cfb46860daa86e6fe64faeda19d7e329d9ab2264b189ec25824f526171236b559df96d8b714d13dfd3a7dc4f253eb6192b1efa516c4952953a6cc6d538672411306cd1a65ca9439bd4803e67280e469a9745def81d7aa5fa63dfca35f04fab9e0923ad3326ce1eaca57a95d07721a68befaf43b3d0caebc3c2cb9392b0b1f7db3853265cab47abee20b573c90a503de6a7d7c4fc53d70258fd1009242c7986bc47908c440f132e6dcf30388e3f11c8fd74bba5e15e4bda2b292c449d2ad1bb0ba74693a0604bb4ef3696ffaf5de5573e76e5c286cbad380982e8cba901e2621435e783e16b319cc6f4862d390179ecf505f404a9740a6978ec29be9d76bf066da8624607a093b6555d6c7e7ccdc47bc698fd46a42ea6ddea290d265de3421f298e9d883ceddd8e4af679a907ad42ddee2292aab8260371dbbe9d87529731a90fa2d669a175f731b4d48fd967d2b3d3ea787189c2bc0664f6f83e1d36bf04693b7a10e7a5d3afd0c0632fdf5c540da4ba76c836dd4b93c9a909ae598391eefcde0bad60d905a85d7554361288999ec693eae0a88941ddbda3ffb190db3187bc47e5da507d13e723d880a4901c357e94e43c1d928e54d9e6a3dacfc8f9e76cec6264f7fb0e6dc8dcdb4035737de0fece8c21baa819a1397854ada72694b3373ec2165a76fef0997a79e25cb4d7a4859b84025f74a1a124a7a48491706d2ee425b67edb3b9b2a04cd76a6ef3065b7b4dde609bb8f4bb4dbec436fbac9519c874edf625d9a4243026a4eb2fe9ea62da1e972bbb7cbd5623f3a6e520d859ae529a32d07b4fe465b5fcb258bc0549954dd2756d325ee5eff5974df22f8b8c1c24ed254f4fae2c6f2a64814a5e161b5396a48bc2d96bf0e910df3ce2d42dae8d04573333a7d1decc7bda4c967afdd855e30e996766b2ea7a599e02ecb59b4d29eca2ce0d7651479eb6f7244b968794854b963b0369b660d9600a105f7aa75d0b8824da064f7a00ed5a484cae74d6f4f1ad27ad205a41b482680561fa924453b3d78cca7e8c31c6183345c53bf925d37ad097e50c7c4fb1e91946f1c41d2e45038704fa120eb51f2c6f37f5f2f698b755d3bce1c0b2ec3dcb3010bcc9b2c7188ffd2fbbc933b6205843c88c1a7983ef25e40d471209795b61c1c9db99a091b7140f54598914a519ce8ae4c00e4fb36a1a12a27fa6b7991e0481a2c46acdde3309a64d9a90cd94dd74ecdbf55af326bfb4e9f03b3a78c6ccabcf516da6bfb2db929484e50a13567830ab9404f014a02bbd296330a319cb59de68dcf4fac7e452dd6a279b5f463ccd1bfdd384c42e4f01f43db1b30de7a66ff24bd7a3f049f3418fba89caf8738d1c4bc8246455de2ecc115e85934df49ae6231e08e6fb2cd298371586aa0c7a9b9346ffaecadb4e9b7e615a8f8ac28f067e4ee043a2d1f487a36a3e50cf6ec2f13d7106be4fe199dfe096816df0095fd8de2db5f1f4c4d11b0fada1b951679e06c7ea4f99f2967a79bb31bbebad488e97b79da6a171c20b17a874ecd6cb0ecf4e692b92632b9263a76986d5675856b12cbdc80f441c3856ab3f957a4cb0d3e768d3b72239dafe34835fbed86a3e4caf9a90ac4d7f4ce2dc8942c70a6ecd9012f3562447633f656bc2ae65e9e5e1703dea73a2e35ae9e525e1bc1ff114fc043fc11077b31ec9ddb2ce501952921f74901269494b9a82a3a0fa00c6872f5ab66bfda0840accfd611acbc7e650143c300dbf93a4e1732ca086fb1c51a49562e753877c6a8c31468a55f0d3a76215cc3f5c0b749ece7855f072618c515e05191d10f0d1276a280cf9443ce4e383961451cb960f4b5a1e25b1ea9dbe05a1230a381f787ae713b1eae51fae0578e783450a381f22177574e4efe50496f339029a57616610d4e8f72144f17007cc411d73ba396dc52a56b18add6119085ab8caa8084bd0966c8411f6c458f17661139bd8b7d7533ede8850cf4b6967068a325e8faea0dea01a63d4f8d26a128513ad264da46e132a77ea00854081b1a486070c1b8076ad26336852c3497073f02c003e6765a3446b88b9d66e2820edd99b1009c96e3520d96de9d99f668f6558bbc54080ea2f0b046dbdd3b23fad3ec340ef18762db57a0dd767d7707c2603a56fd7255661aa926adaab9e9546845a664662cbecfac98d4dd1d8af4beccaefba6b51d1a5b15cb23d31cb886bc0a2916c1f8c21690cdce623564d394bdfa8aa947f2e95cd3f57ab6afeb9fad1fc73756cd8af5df74423420de3b3843424212902abdd0ce6e64c0657286de55ecbc996f14049c7961062e818630f9e88d2f30ed5523e2bddb54a5a08913adec64adb5c7b3c4cea9adf3c1e76e97a394567a7513afb7d10a874ad9faf4b3a3e7e7b4f3809c2989ec1dbeb995bc2db14523abee6f1704e6c5e2d486cabf9b83d6ab299b6c7b4ec693f1eb8f1405f416a7ef393f682b8f29b76ed6526cd87a669da7ba016c4e6da4fb462a099c7bfedde74a8f9b89f353d6af256fa9cc137799be9eb260c34ef1a35e38106c2db0c053e440e3e4051c5113a58e2e39ccee99806c4763dc51876d27ee24f782bf5363f6383b71285a5de4a0f41675ba9af10f494bf58cb5ba9e72550049a0f41f7b8ae1db3c1f212cbb8868164b81ba599534c3521f555fb91bff6f40b5bac0a72facd4fbf396635d9237589b36b18881ee874cdfe7ae946e91653bc3d21306e34a75995ca3f436dba0a88cd4fb7f9e9a76737983e33d1f80db681f2333e31cd40d967cd4ff3a8f7444c663738a331f4c28c97c0b9023c9f9e3803cbc0291406caeeba060365c7324de60992fdf4eca79c232403653450184ac2051486865ae6cda4456028092785ba05ec429786fad24a87da853d8876e958297b106d23a7ac85b6256ca446979268cc85b6570641aa8352436df3364d3987f67cb8a5762d29581db34dc1991438fc1edc4d4627e1caed702d27ba347cce7b4470628a762d2758bda35dab8956afa468357441c3ef44155cd76a026906cc411bba25450d0d77449febe0be0cae5cce01c3c205a75d0b0b152c44606992d3ae85c504368490a35deb4a0fa876ad2b2c68c013348ca86162c2210a2451b0bc306961a206890c8819ed5a50dc40a35deb8932ad27b4340d37d5aef504151aafe266988678281d98eb1e98eb5fb9efb9a73d8a1befa26c23b557a0d14eeff01dbec3a701b90f0e01b3f0f1528ccd4218e77d70a18d0990f112db380077a137f4e21302361b091d8e406a0ce6290db7ccd1163494cfe74ba358e8a0685192d68084137994d45a48687139304b1ab047c37d872b26f08824318c4c9a54a894fac4c6a01405c915062bc28a115a32164cf1349c297065a28149026c1cc089ce7025108c88c1dde1c7826b2f4dd22aa20b7434251360af871eae9321816498b2cb521832f01430fb491e92e4c2254bd27b5f8eee12f94576915ee41796cdb04ba8e9a7cb7943399ca6df9ee52da7a98da679dba1f43b2d557037d932252fb1099fdd077c267334b5172d92a34bb2e7cecad51aad2d3d27c2584fe773a4c4192abd4984d42a424a8b881a72c82e4e7e99a2e4ba1611442d28637aa65d0b8a1612ca140e0350a8f496ea2823928e2d28513ac28e1152f8e66db85bea3dc29d245090a4baa6706aadb5a2ee829bc249a16e80034f01afebeb59a8a9c77232c49876ad21626803b46b090145cf2925bcd1a58fd95b308488059e025ecfa7b0811984ede6208399587a0e1ef7eed2661795c2813aeacac675e1588a314208772ed933788561275c316d6df69da7715d2bbda4f5d04a1a967de54b0bf24e58f6854f389a606b353be157c3c984a565c195f7367c3d0db650a774ed3048ca2e4919c40165ca94e90c7b09cb9697944d71bc540b1e54966198c93dd716ae302cbbf692865529cfb9b457ecddc6cecbd78754d175b715ca597aa60bbbaeeb8f494379f0d042edc2f6252c9bcaae44882092990b57cfee70ddb35fc8eb57be8ec3bdae6c73ce0aee96d35247477e15e58e7bd9f9eb0fa9af78f90b1b101b10c37d6003e0e55338cf813ab2a79a40fce91580278221fe945d3c0cf0a63cd417de6e5fd7e9f5d243ea4b5e70c37cd46f58de721a7bdb694d9fa7cf1a6bb319ee66a3e74bb7b143f349f3399fd1cc600757f79d9d707613ceb2c536cbeff1d9845c68d98af0386c830e3be734325dbae3e96d28496bef81af94b50c941da86e3c526e59de783afb965db751c769cebde4a096b7ecf53d31c3b684371bb28a10524ef3467a5cb74634700de9f9886bde5ebc94515a9c048d1e32ea76da8f2b42c74d7e6215907a5e15247e3ee699a4af6bb7c140d76d4e83b7eb34422ce9795b6b666077ee469661bc4921335a8cbad37adc600c5789d1bcda2138106a14703e3838f805d54bf07c4a9a0f9bb2f6a9d32d9e313521f3b6a409b98ed50c03527dd0dcb91b9a90f92dda675590d79e48e3aa63d91dbdea97f2290c6473247519189e8137d3e55358c8bccddb25e45e66ed3df1061b790de47acd65601faeafabb0c540da6b8ec25b8d765a3a61a00baa7edc1429338bc9ec18661f441b3b8265f298bdd3507036ac0ad662a56c5e06a9116740f559c2558e1d1f2e6cd712c24abfd73b21326f42aa1f27bb074ecd080b44241801ab9d5b39e76cccd84302ef3203b907f31a05fe428d2f361feacc5973eae2a0e63fc6958f6f074ae181125278a044114ad0a09f03949041aec04991e161ac60071c84b0032423a66c41e6053f281a020a2b8ca82106576a93304874a0a530c4d45a6b0d03a9c700c38b1c6892deb502b11c4428526bd702a309183af43b46b3161848ba5deb0b2ffa3d73587c91d4ef0f87fb962facb46de78593762d2f741840bb56175f1accc9b5369e2fa034ac4143db90062beb6c50edc776ece86e2345e70aae6b81f9d29b6b7d85d79b9b12010a01b8e3f938b7510ce9a12681874970a5732ef7c4cc93c3e5e9d8031d8f232bd89e0fa20d9bc23713ff9a067140cf6cc4a1c0448787c3d11144f8dec562b158527c9c95f8acc41d72c9afbcf1bc94b71dd36bde56da67de9ec2f269db3165179f6517e3e592b803f2f0ecac564851e7a9d4bd71c807c62b8e87eb78ecb1fa075e69a1a397afe83c2bafe578c8c7c97aac27f4583e3d51273e06d5a6b4e22da836cd403d6036e2ca34fc0ab561de864cd0d7f9389f991fcb273a31fd2eb7da308813f303eaf8f1450ddcca75fce2c8ad5c6fce071d5fabe325d629739d9876bd392074cc82480c95322d008386964f3422082dac5c99410daa18e1b38021a278f9018c135f9af80cc9a087317a904118305b74e1e39830410d4008830b1c6060c2672807124831c31258c45c31f289df893b6a28f1c50d9290a2046168e11385f025081e66c821cc12627ce27be20ee7b3c4da00737063c2075de4604a1357584186c905c6548141134038c183146066d0032348f1828a11acd01eb00bc0b4c0d8002649864d30a7c02c1c82d0858e9b0a2e34fc7d7677c3e62dc2fbf8e0cd2bbcae4060d7256d5f919a47c71f982babb7781924011d33bc096231ab2d2fa234fd94d236662f833fb52742ad0b2b5d24b5fd4cd44a874078348cb9033cea67de03abf6536f5541ecebedeb4c29c876dcea4df874e7780015e1d130778047c3c37c043ec874d34daff74e0b62e3c5d9eb863dcb0e689e5e94629cc3e59cdaab48600e621cb0121e1e7c83b53c191197da2f2c2e64687d1122ba2e36a6c08186a226556021039fec47f81283dee49d10323998b78473b2462f4fb69022021838b183151cb4604b18ca05ab29e5c104247cd0c30f5228090306ff2280762d2f58bc38c145bdbb5ab87e3a865b382272651775ea4f6f016a7fd7e5895c97f0d963167acfaeddf4135897be7ca34efde1cd67ee6f7ea1232232b01379c7b0bf973291d44f59e8f4541e1275ea67b2d0119199bf2cb4136ff24c16ea893af5c22cf4a8532f23d788b39bb0267f9d882c5de85ebe9489c46759e825893af5e59b853e705d59e888c87598851e2b27113a22f20eb3104be88808fccb42da614e21ead40b99eea28e3dcd4ecd9d8f4bda1ecbe6cee755d45f126983858e86a0b2d0f3b99f790d6e01fb751adcc21111ec7a0b27b066f2cd0f29ead4fa1360b8795eb98579e7b33da4ae3f611f0d3b22f8b7450c5b2c69cc41074d3da886d057e0f9ccfb486967c20d8e8839b258728316536ec0be68f1440b56f7002ae24effa83d166c393836bbffc0d53477c07d1e02b93a70757c3c59386908818ac4673c2f8b25b00332cb11ab5d86f34527dfc2e5575491f32adcb1e0e1a9750b22240278aec5a2b674c1228a2eb56b6551830bc06001a6bbf4fc0ed4e15a5cacf47c8e79ecf4ee8302cee7fdcf65a1a321efee2f0b3d9f2144f5563581982ba09a407c3d9198858e88940e1fc3f38959e885e7035fca442570804f7c0ccf07de4a284d3dd8e19da8337f44240b1da926f0ee4ee4dd65a1237a21177588bcdb2cf4f2e3fb09aceb7c9c8f238a49f208f85a5da2b4b4f0c1b5c8c991b202b8054a0c395b7e3841cead90cf426bd7da52842d567adb9964ae486a5d71e50a2bb75deb0a2917f8c176df5deb0a2687e25050e928df9c314a3763bc944e4a299d8b0e462833088dc40cae1c7c3146082184114a079d6a4a28a594b01d6d18e3a98c51ba28a593524ae7a633127bba197f328439c3cbec6276a587156da71b1fbbd8ec3ae79c73ce39e71c640283598c114ed1f2f029e029607b0f9241b6eb83645f3faf4cbf573a16238677743edadbbb8bda57caaa2032dbe2df679c120ac97afa57a7caceabe894703e4bcb74cd5a98cc5867400592be9e5d94d64a6bad95d67929b3476badd96b0c487e6e0a88235a99cd8b6adb7520094ba5286ee91008b3d737949412cb46ddc8579c3261a7931664288a0c657a1b8ac262b50ad15d79932c56d77ca4c56af9a0cb0aab2f54c730accd4961b5cc321e8e887a38e2dd11babe074a1b7723de692877233e0817caf1b81e7f695041ae3b713754df743c75ee062ab60d4ac68561a36c2e6b5e47a154efa2b5a5bdcaaa3055e9ba6a5ef4c97ab9a905a9af5a9454d1a59f2664284a1256ab30940412abaf4bed274261b54462b5cc466218565fd9943d1cb189fbae034d4d441da13a4a379349b823054b4e3abbbcb45e7bef2dddebde8b5ded66d744c4b465a037fb6cc6495eafb7525e97bcae96d8b177954eabd6e31dddeb0e5e19bea694d259a2224a679797746a40689c40845db9d7b143202cdb20d955f25e5a8f7ae18dfebac52afa7a62c54662d74bec5b4627147519c3e8aa91220a298c708a88785f8c1c155a54e1210b24b490e08a1c74b8f06004152040c1c196115841bb30f16284c516464cd860c388d78353d14d0c8c5c17b7680c235c444251038260a485568485110e53e460c40054d10c8a6024a36804482f08cecb124ae603838c7be2065188b085910c8ca2aa688b9c450428f2c2281761792fcc99d19e73ce99d9312a16b7145f2ae59c65bacc502d1ccaa11e75602ada297a55ac783368a1c2c31546618a0083910e8c0e90538415392b84784f94311a6384008963a6e8044462a4f430c5e80da10aa31b3adc0f9c30d229d2c208464ec018b92c460228aa3754c69420892a35481aa308313738616224051438f8d09466c1a484a21bb2307a91109874f18ade0eef8b295cab545483918bc2c888a74807ce0a16231a452a302ac228893382018a28a565c6684ab1684203a31766556482ab62534485d1116e09288c5c13ae073a7c61d4554c1006111064c25062db52849431b454e10409583ba05c6a30a328082ede10ce0ba305f45b92c3ca00142292c80198255550a08329587c40022c288841133e3c1881134db79850a68973ce39e7dc73ce39e79c73ef3b1ecb27e20073391b0e2a8cac35c61a638c31c65af3362505a3c47570f562883a29c8c35ba0fe3fda7eb3f5b122ae9905b87a42f2722d220eae6284ace902cce5b8978083bb2da0e3b76785e6241c00720139f171893b4c78cc5f49517d3b0fbee26e367e5dc78fb7c07be278d45ce7f129c41dabc7b3107708e03100a5c33f262b27253af9b76f4f49e91517e1e9223c8dbd8495ee0a95622f77bb331e8fcdc0db6361ef59c15a5cfb230243a28455e28b9a91b799836ad74cb3145ee11e0b7c800521560d0d7e4a90ee09bf24302b2c8000e014a2ce3b00f0cbc16f49d4793701678c5fa3048c63a373628c3fe006a00e9a02267ab045bb7694bef75e2e41895b3a8de37c7b56548fd9e31f986705fe71795d3afe07cd4578e8b196bbd968a8c12ff95d1e8b852bf488dc8df7127e4ae0ea3dcc1b8371f08b41659f8310420883b69dae416f0c18b87a6701ae5e17cce5c2d7ee48c3c33b74034f3ec09c1621c703031c50e13c81d9b883e8a83a22f047dcf163bb3bd9c49e82e3114df29af02aeabc67e77995669bea81d8853884410885cbae1dfb80e321e46e04a1697cbb3378e20e1e0c0cb1d7b0ad9587d67a93dd7eb4c934435363636f527790be044c027650e726cbc00eae6c322a6fb2ed77f286d3f630a7edcdf599ec6d476a7833e52c0f51e1cfc07107c45b6a984c3970036914523a2bce39e79cbce29cb33d7072a0768c3b608ecb9c938aed11b17989619fd385c159dc2bebac62d4c35e9c21fb75cdecda5b2a1d5b614df64578fac21576ec100575b043ecd9cc8132073ced30cc5a2ce3b1cab3e5d83173aa65a77f0ed4819d7e45a9b5d7329cc23970656d0a8de5a0d9d8afbc822b7be5c395fda954737dc51a8c449895622eee661febb1c2740a63912a5dbe23121e35c3b0306587195816429d152a4cc204d602faa19e794efa49b38703da234056802f7b38cb95b607ce9265b65620f4311e81bd8f31a65d6b0ca4ced1ae35468c361280eb1c8001e03a9c63c2253a92f359422873bef10fb0745a8ab2d47e5a3b6fad95544e7ceb898ea6cd4097e1299873ce8903571888bb65fd4961ac71775280fdbc2e41555170556feb65cd2e2a75e4b5bd8551e64cc2253a9a2f95e6b4a5493fa79db6c612603849c870b9407c4da090d84fd2c32019c8407003c97920f2f035aec0010d2fa4c75a6490c0431b4977b3d1aa998dbc8e3ff23a3ef7f0d5834d602a1a33eea6b5f7ba9cba44471322203eda1f2e8c62eddddedbd5f1723a97bb116dc055fc8e1377b37175bc8d9d1c3c0bb8b901f6f35ac68db7a133bb9f73f6506daecda9887db8d91d52818100c35035d7dd519a9c190238e72e11e7f28900262dbb4e62d7253a82f129ccc106c01d0fe6d27d09c0ec7592521495184b1bdc2d6b0a53f8c86bf9430963bdf34447f110860ca7630e5c6120ee869373e435bc84b9072ae90830dcc49df7e7b59cc1e27592cc95762d32ad2640bb1619254d7f6053145ccd53889f5c89517ce4353dfd735a8f875d8b0c92761abc4447f1f558c5f7b515c7c3e9a09f4d3f067bd7845da223a223e77460ae4a80d2acd6fa9ce6031a61ced9191468f004ae5c6763c0f46bfc8ee8f754880f1fdd0c5371659431c62bef0fbe07df7b6fc77d7763baf483ef1981ab983783f8e52d6b183fe79c0f1e5701658e100b7aba923e6a40e4e5b1392790cc370ec9f774d06408284b601e0200031149556ab0840d59dc10ae6882821964f1c31739a045381973853e1983a535c64a8e0e592d8d708ee9414f31b264920b964a425a018790ad3146d83a8687a9431863c2484189cc0b557010c30f7840854f184dc520030021868a9650a962c30b4d5441c609122f626469420c2b45991852daddd4ae25065267d81263496fb62506927eb7485a63a61083456fd0f449bfbb777bd345125cc24c5102871b681037bc20c2862e3bd8420318e4f0601bb00d3c928c6572c5287291469c9b6a411454ec80c40e869842c30e55bc1082881c6011a28c8f94456859a3a50d5a4a99ba36565ab488f1222ec1de10efd58b81aa6286d05bce082a9812d41fc4481103859896944e88d5116f8f861bdbb5c444e9c78262a4f8e0ec0b2d8dc43869d70a23a5379c9e615ad038af3bec039a34a844ad9595492c19aa11010000003314000020140a8644229150381a11e6a4ee0114000c96a4446a4896c7a21c46511003410020c6104208000018620000c62090d20112f025231cd17cca3aec304d174d2ba2afe627f9d8814b87e87518220d6d71901109b4384a421c6dd529d4ef8ff81119a839b9aca92d6681ae4a99e27cc30ddd07838696aeb53aa9bea4bf0f8d4fe9071d29d2456351f677e05ed34af4ebd82507eee328443a6d0e32893bcabea6350d297b0e22acd2f0b424cd3d1ae0262310698083908548839b10624759d6694c14b2e320c23a0d4f0afaedd370cb041d74d5811074698811a8e018218e76b6a8001da77da3d10f32f201eda774d0912b0e7be390d6f14a8d49718f43adbb688cee44658731e4e0d6d1b5a35e47a5eb20718576a6a511edd3e2904ccc31d29af674799f83afba1ca1f46404451a10254450d0b078298b9286c5c9084a1ad1b2220e1a8f9b3149cd75a4b3a0b29dd8efb2042af4e7448f1fb650c56797e090805a59d24da1bc7db500c82c6f8066cefd445d982abf889973d132b8225ec80bf93aca3ebf9216a732ad837b0d57b8cdab092ea3512eb452702adeb14d3e92c25a59cbc3b15b79ed95534740a2d6c5cf0fd3f5c625c85650c26ac26321114250a02522a9ec188dc5d2c820077a7cdf816b0e7b3ba477bcdec1f53b0e294693510ee8a0371de7efa67deb42083eb43e241f738ce4a22d6b42443f8d8f48c78e6e72b4fe0e92e8b4cf51d861226d0e2281e3550eaecf31428c26a31cd0c14d87ee0efb3b1e4d07092bdad310b5eb28e4551a13d24e2d874410475d75945597f6456464251a22252b3b8a144f3b5354e00e68bfd3f896f4c1f9300707c84913f7697313436cda850f2e867ba205247d13dee4d9a6903db84e46f2a1fd2df3a0e3345cb4674946f6a7fd940e3a72d5b42efae7d8050efa1cd276b8ea586f0e1e8bc390ee34f0598a70a4f11921c70ef473a3010b32829fe6a37c8083ba769c769ff64bc9c80d1a6525371c490d66e1cb6c2fc835b18186fac2274773e190c43e9a2da57db08d8f7c98b2d21f8faf7a2b873601a79014a09d1599011c64bfa5f5218fe668ee1c44ead36c49316b937649b111c7a3e9b091abb4271177e778ddc1753ba62d46035969fe028303ef86b68941447fbaa1b36464a45dda9c2241c7eb71607d1c24251e192973bb865cdb2fe84df4ef5cef3cce4c41e75b36460602738d97c4ea3a85cd032db32f3c6249622d02f671709f5914db97fbd188ed2be30b39dd30d8127e19561accd796fd61d89da5956109e1c2049087a2be6e2fa7118da7c16162b35321e3098a8883017132ebbd3e123cc0368ac73c90189e3290e42cfd29268e0f321f81846fdc0f897ec5d270a209b6640939b3f29829e13a1a520a73d92289dd5c9f804fa085211a66da6e56956243931d6e3dab0415110815f4d7a354aaa4d9dfb7c530fc378df9cc1b6b3849514abbdc93e89a5467d6c59fdb837414718d4853da856b78c1178a6ee63a59ea7b7c0c9a8c650caeef34f3e9618608f5ecad291ff8b74e7f36fc5a9040c5df3fb0be17e1234f6d2dbe8aa49d4e7dce71ab2fbe9e87b82fe4c76b35f09d4cb9f560ba47d439bfe30446f0d0aa987fa5374b9b23c8d0e32d74b343ccc7be61fcea1816d0b823a5a2ae748501ba27df171f1c80d568a07bbd6110a7206c0bf8e9601e22ce61201d9d037405728561aa83e25214a69ea0d107b20920d2b16b1379c4ee6c69bc0bbf944f23809b8feb4efea5b755a18d8033398e0c6762aeec0561ba23a6df6eb84a98400abfb33809567b239b314b52cc72af8c139382a957b840c78c60e0f92ab7f3713ded39c9a0edd447d860deb07d036a21eab127b6bf3637b52961409d2746e768bd69e08028057ffa405f000e75e97ca4c61a9b68cbb756aee060f7357bb2aaaaaca20000b411603600bcdb3319c8ee4152da1944631297ab3ab918df0d62a4a5cdb7b1773539394026e68dca1e357135793241a12731918f537c6d98147011d2c3d53b9166220b9c98b87135cae02e17db9e04fbaa24e46c4d84a85b44f29a8ec2576bb5f73b2ffa80cd3e398156052c11ca6918e6d85cbad1d9d1d7d01bc3e9f18cd5fa3939bcb06586797614b34799eada711704da6c807061ba529a2d34d5e6caeeeb97f6d53ea8636704e0133dc180a0f41f8250905177923053ebcf1402ef683bdc33b2d7c609fa15b69e34679f4471534cbc1472d496ba964dcc9cc4450115453a5fae07fabff9224d41c6bebe5e13041f0d31723541166a69769de6f75370ddb30e9edcebcbb33c4b5086730304edcb22b557265350a3de5625a992706fa1811a491238b741a6f08b7ba653f8c9db7ff162cd9770d039efe099faa5e7e7afe600c8386cf38b663e9380d9c9008288c77f297ab612df9deb74d9a3e14caa0f44affb753f9b28e0836c24efc0792d34fbcc0092f2dcd6b10e003de3d2176bee9f94a481f811fac2da21c978e37f9e67189925cd4041db1bb4e0fc74d3c7a2e9347536b4c8d29c5cd5571e5e1aa7552d1eb5e00f35cbb300eeac3a40812200f554ce3d81b59c6d55e29c53b4bc23432af5c10fa053c8589ccea38483cbc59e98ade4ecb26000f5e30c09b80824b37a037886f3acdab5004c3b55e5f70643e20a06b6f2f844f9fa3405918f0e6e4ecf24c83a477858f64c0dde4fc9c011b3581ad75ae5f5700463f43844997e680e1386348ab6e2bfc68ae577ac988080c0b7c4d711f7b421d2e1cfc4d4f3b98164608098a37b3c965a0baa5a07d4b8810af1bea2edf9b7b08ac8e9a0bf5f5ec82175cf6943073a801c2eb51bdb0d9b82f82bebde1fc7d66d363ad91f3e5a70fbff4176812dc7808c990ce210cbae71ee166f8271263a2e8e3d7d43f6e349643fb91b010629412262160245124a426782b3ce17514ebc1b711dfa25893d66ef9579ebbacc157d2b671f7ab135ad92c8797bb9563a3d73f6daa671ff9c045f4fd9259dc82d8f929e63d4520dc2f0f8177787c64ddffe2e8feaf2727e4f28c233055ac469b1f10bc2d05b6887b3e29326e0ed31c8a5d4184f5b2330360ac18b424620c6dd9e66373e379e6a8701aa3d076fee29aff73cdcbe0ce0a54216fc397bab166b499cc81620c35bfe7249a0212ee05061da1f1cb9bba0f2376b4cd86441e87a8ff99c27c865225fa0748a3f42ea848a0c163c65a5c22229f230c196780a701d0a6485fca18e6642930ebbeb6bea7428710a9d7036428570773031bdfa8382e5dd56b825a6ce752687d256d739077ca0fd54819e3557da5f9b219def147fbd6841969898161be0c6dbe033c6fab206e27c08efd0302d68777b4de616adc9d50f4e7dc3fb43ca2182fb47607a6b461313f68fed5de0efcf288b53a71fc5f6977b257771398fb2d546fadc25959eb304b4d3aee6e7aef90f66453e1355b7635e52bb81d3ec5be56acc7357d2d8ad26439fc2cc430601b6fb470e2a8dc82e134c040ba1ae84b91576c0a6707d4cd9e42e7aa5ebd53380a8bcc47c808a270e54b88b9e9b9800d54489a42edc95edc06acd894aa11a8c0a6eb8aff01500538ac453356e1580f93d07f9a15cffd2a69b3f17224f8c77d47e92d9a564117c2574cbd08f73720116f40092bdb326087dede370456a7a137231716bce008a580bc3f687e2623f4ef0c11b4c1271ea831a2683a2af8d2d6fb23cbae533a33348c160f7dec8db61f3c0e6489795c970e4c548741558b40b532dde65f44681736a442a0f03d19034f0dd0125b1f8786dc4f1b144a71e0abdf04c5bd77c9a052d947032752280758965183e1a243c0b8f498c032a108950768cb0f95230dab3086fbdd9dad518b73acab452b5d0106273b4faf02cd43c5df9bf11ea2bbcf2ac9670d807c3eebbb0f73360e54ccbb269808b5051ca4993df434d768f88cb36bd3304f31f3b9677c264a6da17b55847cc9d0ebf18d9fe2a9aebe51867977f98f83d495f530457afc25a64e556317583b05228d67b7607717ac193ca92f0bed673d4f489a7118b4176d94b6564d35be28abda91e6fd4edfa9227146c830675e1f9c9641b451aa8857762b7b77499feb4c3a3692a20bd80d70102e0670afc174cf98554896a4b03ae5531e9334a71537f18364fa6f09375d9b3a428bbe4aa9444f9bffcd6219ab7c3247eed8f1dbc57fd5a813427a555b8905f193c91339352700958e9f244d9f92717bf9c12cba2f7f7344c725a8c820fd003cf2f4029910d8c20607fb5ef89cee87c64185da3694df063547021fe9ad7065a5238cb15cb5e92483a23dc3876b09a08c23ee995a2515e6dce56eccbb79567d127c97c98e87c052c2afc3326cac0c36f67dd07e6e2e1afa7df2a3fbe33903290e6dfb3e7af9cc5ffebf17542ad0189578566806a621815575f1fcaa79df4b8aa0f9ca6999e595f9e3d3726739dbfedc42085df84bbb8a9428201248d618f4103bdbe378853fdf98cad13f6cc84da980c218454177b78d17fe4a5967ce0812234298181f64a5f27d4c67042705b031caa5eaecb4cd933f4080bb0a81329fe20633b44375b971eb23d16df0670b67aa341973f49d77d0360c293badfc27a93b0a581e3ebe7f5003357379f922ab032f1abc40b13bce7117f3d7b7639e966793d369d84ce7e6fdf56e21270c9809141226ca614e827237eba3f6958aafeba242371f14cbcfeda4fcb7d9f1a25431d0f842052f79668436b563358c9fbe0e1afd6164930ab4f86d6eb804bc7f1e23addb9b961bbf81c2c8772cbb485f5e1d114a17315200db1d4812bd68071bf16918ccd43100e51a057530d0af1653c36bccb07ec3a84f75af8cc074e814ba8a918446ed460381532584add66c514c7313ecd20b1ca0131999e6364076da0daf20eb0b6a44f1d73fc6295826948dce730fb70e8a90efdd800a3991c286af051c34ad7f68def9d7599bdf3768230482912223cf858c55bfac7d1ceee28634fe15b93b7173e11dbab6df3dceccad7e6015ff3626f4c5de857d042395ff0b256a181f8b68cf5fc81089bb371e928054da8ea422d9d882291b17159a5243407296aea1c7ca3090ac5621ca69b623fc6d5b72b10bbec913a8d479fba9378a411acba5d61b3007bc32713b129009f62d8a8b1c1578a1f53ed91c7b72b3c98c492983b60c481133a1ce46760c473717f4d8736c9bb152661d1d2913ebaf757855f405962dad3fc2c8dcfee2c2420543ecb05d30f22baf984ad5c119e71634120e79d6c46b3e454e6f8f3ce1ecdffcd5eef54094c94284b7eff6a118ecd30c5382c9086dd56b018e10b3a3d230b28426f5b8d9c696a3d0b0638e01996c0be9c32a2de999d15e47c8f963ed7076717e713fba7a071e2b50c953772eb9ad10559c1d1e56fdd59f16074989b0a2843c9ec0e4b87035e1e8330c6b79698f9784f4ba013f96ad5de2e961a6cd651d740c84a2667d9bacc0a4e451d903e412631f43bb3c432a275c731b1f21a4da75e5a2ebfc245618f0912414bb40b55696ea80e6031bdd42176982f38de8bf64b05cf4055495af246adddc38638e4468849d569b9841570a76deed0ccf7960d6f254c87d1e62767d8da3c4c0520875a3ca52b84a23db139af6302a3a76ffbe365bb610ceb3f4b89f3165730aeb1f6a74e62b9e7cf10e5f07b0e184e31be417a49ca5bbe8fc4c2ba720902ac032de73e4c3d5869bebd6ebb980cb5941ca7486a8565f85a56cc6b2ff12c7fe8cbc58929c99460aa401fbc693b5af71547d193dfbd52e78d60abb28128070657a1433fa0d31039a610d9e66d1b90757ecede92ed5c324c65196146289340ee74382d08eb86764e20712dab2d17c022b0cdbbec10debc0f0e9f0285f4b1410c11756d6113cd269990ccbbd4216a9908501112525bffb8e6904975b5166ae9211d05340c0e1c75188b5e1ae53830d7c4a09f826e1112e594dd616456280e89275f00ecfb29c129ea8413b77db7dfbd226a8b2ebbc80f4d9446f0532bec693a20bb6243bd17a3d3bdf76faf10b9a4a368d4a1bd1d6cd9e3e9bea9be820aa2681f748687d35682332c419a873d257104474d04ff45cb350f9256c0b6fe64fe6728a2622985987cba8470d42e321ab64de3c73941df17606daf837af3bb6e174e0c6cb90ddd3bc744dc0151fe091e77d09551aca295028f069dcb6e210d3126148bed21392556ac1c5a96fde6c61c7eb4390417ce66ec56a79595cce0706ebe54245bbfdee4b439395c38479582ef696a112cc7aa6055f283f4c7b2254fc7e08dba8a8b68eaa6f209d76da541f85f122ed295de2b1893a574f12792666b2e01053af87210af24974faf63017d040c60f65f301f59251344bab2ad4e8f52a51a59684835275d96758207cae1d532978746f4db2a0d90c16bd57506de8dcb82e9d70aaf680b1607f069da73a53613c06f7daba304a984d624be4a124d12ca04bfa13bf1016e49de7e475bef8f9e34c90387e1f477bd7f87b28e58aed7c5051847c379f9fe7c7d1385824b2d85f952c41ae8594be6ff33542c5fcec0283c5b665842d4439ba238b3248b8d3c717e5a5392021fd56cb7fd6eb118115693090f32fb98f627e82d20ce6b87c3712c1d8b6b016709e02d09438a9876259fb3953b2eafe52b08142b130fa5057ea5499d64bc932622d2eb1927393ba4a032236823fce7b8c024919e1734800a3291e9fb52aa8f48677addedc086ef10c3ea547710b8f71bdc7e3936cad75279d5a9fc8ff28bb1b692172ed89008e82faabaf59ef0cf379e74f4468db3fd59c726baad5493cea32ff1167d45ca0a0eae7ba9a9cb58c1e046651d3471a325ae6f05d509dca3a3fc93c8981c758dc3da3110a3455eabb275a9f9791797e0860741cd2a167971e4e7c16cd8d39a3c42349406f936afa94d669ce0000ddbb5d57a0a51b0d8c2cba92ba16b7ec1ae6b96962774b2afdea4721f1e4536d13f2124a13e29917cc06b25745ba7542a5c758e01e02c5eb4e478cb27ad504476827559fbe74ced118e282bfe6718349004233a00d564648ca56dfa3f7e7afb6a425c64d5a5080f95b704f6bc56f2da67a1bde0c03aa49177d5e4451ff55ad165519ca518c83b09736747a7bcdf72759bd027d6c02409bce0e62a66738d24ecb8e195e097bce8a299bd3673d58d584b7cde0f5a89d8bd9072a9b0f2414747e5efa43aa843e2e3267e8fe0ce9573dd2b3b6520407cd508ee08b2f4e444cc496cf11ff4af2dbf9c05495fffa80963b15438f7d8c4f7f337e7241e38a960a858907f879e8d73b2bdff6ccba6377738f11271aa93413dd79bce3ce08a87b464fdb5e927034f9f76138cbfda1420b0886044ad97a5be1742acc25890ba2818dacf20dfea0ae2b6c96a176879ddc9c191848508884d2c8a900f294d519c9c67d3eac01dfdc9b460671736fa0dfad9347b9216ebd385750d860e0ca57798782d24a563f63104eaa0307224a4f7f4011d421703812c05a894c7c10140b88008c04b8bc91c3e3f291003091e8134a10987402c8d99330d11e05200b37c5035260903f63408c4945f41f8b85b2fee6382801146f9737f48054794d63ff890e221f36dd4fbc8f11e3dbe2894e13ebf3bbd38af07c5d878039281a0d7605094cfc53bfda4d85582fb15c3ce11d4b3b2b239eb70004ca99f7b7b273c1fbca84a85cf0c72db799ef56e20350a401272fc5b778912e65f9954f50b37ab38ece4fe2b4e5bfaba78345524d97d1ec809a0440582025a681b4607247b33b9e904b3b2402a0f409715674c110749e5314da4cb9f5a4ee8e820e5965df00d1957d7764a11ec8e5adad2b89f1efbab3e4f5efba388ae95670c897a10a23f9069a8c4e578ea118ae5b8b6ddeb6dc4e412a8b43c338532e1f703d336c08295224a4b7c2459ef0a10bcb187b0115f8febd85ba4abf5ccccea00aaac9e68956fd9fe0ce593c7602aed6f61dbb2ea96780f87603411f66ee13974b3ccfc2e3b502395df4be6bec829b0baaf35f66f53f2ea51987a7ed760b1866431926d9c8cf694aa6bf505330f3cbe773ec90c3ad86031c0238f01e75a125649b1513cf0b6be2d36698ae291ce57045bf0de0371a90628f98ab98b01943129f2775fb0b05b016e0cea20e7187bf497b6e4ab3e35eb7b56f144338488b1cde2adc46f856948542a350125b3bf42e33393657811c3d6fb7b271c34dde65bda8896514bcc7310116c4dcfb5315f689b87ad2078c39bc41487e8855df6cf09b42dd40707827d18afcc29c607ac2f573fb933a4e43b8d0a592ff20d63207bdd3aaf0ca9318ae6578b970915a452d4df05e119a788879aa813ab5a4734ef24d07b0deb0370cf5ba5f6bfec3ac9c4e2321319463c6fb76ed5e70486a86b26ae416e0ad356c0c1eaf56211be5e00118efe3a03a7b753c8a66f2f34dec8784c9d5ecc1759efe61455f8d7dc1a5a88dacabc735f203e157509f9984f1e259d2f6ba150198cf06aa99df89fde90faff8814437541f1c3422d48f962743386102c56bdfcf18f1e39675500beb8a2e2f181de95baeeac00bc74f945cfa6ffbf5b69fd8c8a7d9dbf90e1ff5d6f4a28be00c9fbc7894717523960b0284aad3b22d0c3f2971870d852b32cdfa51d5b1846c124d824ebfca3300383bf2e053a12e8a63b1001af4344f18535c2510858fe6d2db712098ace7b1ec697230992744f055757e2784dd1e9ab6b55bf2137979a1cb9654eeaf966a788d3cd8eeb9d0f695dd5f4aab5872a2c63d3ae2db0d948ba218893f29391ea99c60b16289d9648722fc5bad38bb236a67c8f32e6f757218b8f53eb40b8304d9b54650dcdd14d07746699bfd127c0d0df715b93b80b8658160ce7bd0802db9a0b0f511ce335d9d2cf10a912a084714c4394282a390ebd3bd7f329c08b0fe88de15798928bd400b77fd8a3604b34cbd7013f5f2bc04f951aa5a99884314405cc7b86d3c9c97b6487429776461d321970e572910ba81f7c7d2ce959e77be42979539a4a1708a40caeaec04bb5fa57d92193050debe2914364346fbbd0bf997066e3ee6a33cdc959068b8787cd8dc168f416a56b3393d1986546f55f74545cf96290357f4358b79f146e722034b14dfb32a4853961989492076b569cd71e177a6ba8963edb38a1c283a03fd0aa80f671d728dbfa35b77475cd59370cf802820433a366739610cf7f6b51f88cc9cdc399e56b8afbda0111625179b097bc963f0c2d353b5018320088d0f977d2b4420a29a4802c7ab70160ed1b9d325cec2e117c468b4abbe2499985612b3fa4af56000d73899b2e4ea35fd3cd59edcd0ed3437284fb85f188d437840e9d7fd138ed301390b21e097f5265d35d56751c482be650dda09c873904349b6ecc99aed4b56068fffd282d905521a66b5f3596ec7fb335c5da2799292a777f634650a0e37abea33df4d351c891696f882ee775f45c4ecf38d5b0c50a3ab1340eb3b3e6083c1316a118cdf79f8b200c457e48c3adf94412bbbabd4af89e88b50f1d555bfe69f12e65606526b6b48cc754e29de4c39c1e06a2972045e5f8d3081608cb89467d936610fc8d307a5b0ec3d04ad80b9b8c4ecc2d023ef2ee0925e36e6acb7a9177b558a3b800fc02d737043332348b9b6300b7f7900e8c6e43d49c6145dd3b8c2e61e49973f0679b62168b70ca6fd602af53bfad76a586aa7d8911f5231f4410e52d02e027995f46b9185dc039a2ba3c13148a5ddc01bf3fc23999d7192adb5fab1adb50dccd68f6482d3047461d4f946ea89743f36a5099b3b0bf7ec9828a37b4dae579f435747c21f7b94633fd7058453472ef80ca5b72e6230a6d132ab73703a7c464f5175538ecf94005979afdc218726558d86eb298ac4999bc00958ae29be35ff7b71903fb68907a8476cdc5b6a06cf9b180dbea925c0b3206593ff4cecba74afed4c270f8b1efda36f82986aedf13edcc3207492b92a0a1896f0a9a26d3372be6ae08d2762a5a2955ebef234b747be20201ab436025fd953eceec51fa1ec107ecd0468c3eb28445db598d8943a426bdcc821a7abbc8fc111a1e9f182cb4785a09330ee72a9c301f2b0ad96a313867fcc1234b4928411153f9a4aa2437bcc020f84a98930abce6ad554ed84ceccd0529d2b84eef63bdc50a28d0ee5788df460685c432e14f5b93026fe0d4e928bb75274ded636f60117d1819f29d646028aee3543f74af04bff946be69fa0bbad0a513a40c4b7b194f4b3e64583676f1b2c421e25946af9a4ec48a1b4c40106a7758577e1a929353ecfd44699f871b96ada12c384652ad9b319c9b3185146b44e149684f59f5a32424838463d435049eb04c571238eb7eab9418cea7ed086d667286405b175a41a480a1ad0586e3160c5fa1e39700a531ffb4e64dbbf79768966232a2b33cbc1198a09f5f75b41026a7562d747cfdb1c1d789bfebb66f09e61bb68d849cf32687be017945aedd9ac5b90c152baf84a7e87a454fed76f677bbb6949305dea35ba267b1deb0e74545792c9b10199038dd34b4bf2f78315def239c8174439f909fb066b28e5284fbef9c10063c9283a9ae85d0318466e109acd06bfe22c39a2dd486bdad6bbbe3a0072afd8f1d44c646a6ffebe26f175ad65be69ec255f87d03123a564a6097872d7c45a096dfcb1197408e5484444107d0cb7f0b691fe22caffb053de4b2afc7e01b77e4414ce2bd286b44d78cf0d9d5715e0d8fb7e58ea1329e0fa481398f87189206071af5ec54e5f604b99fc43272e28a2b4d0ff063157a84a49121e004fb4c952a3a0160ec0e46f7ad91d3bf189542e425251717041b9ee932a6a14df38a526407c06aec5b89d75db153a2ae575b0750a6e370992d4a547ec07e1348f0e55f7e7fc3b5748fe4d2374cc7dd856cdfe637e58a251233e5f4ebad279115fca9a59d9ba9f64335a3a326bbead8ca8c204ea60ee764270e05045828ce4ed001512ca982ced54c8b53677161cced4db004add00600f736dd64f9e2396def65b2ed4fe51476649e67e1f3c8ca625eef6d697c948b41f038d1352a7ac616187615eeab12e3a79064fb3fc1cf939e6a3bcc7445c7d2855f66fd1a307d00d9b93021698807b549b00760462dd4bfb08a05685e9ad6371d9e167436e7f4d182fb47a751fb4f284ec8044dc4d7043a5f058278515ba8043197ba5511566039db0d1abd0259c1a5370b5c500a5695f8136fb84105495d8200f64f1c946424a21b99c8d0c042b43c2b22b129f15cb96d073fc508a47517a366ae62d657bca5135921b11c1545c5f507299129788041d3082667ff9924768b23ee35cf02cec4431e7b6299158f6bae9f09a6e67a2f9f9e03ea6eb893c34410a7389c5a9184be4b3753a9828fb9c4a4d23d84491612ee8fdd9a36e3e8be967c35899dd6b2833c9beb70ccf4e30d23226c0a26ca0583811eab6cfee702bd28189b7b85527cdd48a4def95f39d0ba4c92420795e993e4f56468c7336362176692ca94df00ac2cd523f4dde735f615596f5e8c0cda37f731d9b2407b509afd58407bb4a03e7a101e19748f07c27fc41ec323a488c582cdc6482a9c4ad73b6982c8c3942d2ed450a4dbbfca89689605ec22a50cfd192a8f2f555840d02a4ca6d167381c8e6d184ced95d4c3f38fd1e6f6d7fb835f1590be8547930865acb9ef237f5ff9dfc27c76e20e3f6d87e7cc65d1f75e4d4fe8e0cc5f5ccb0e62021bec245d18a652478b516ec405275bf48edd854d1e2e3595f650e0084aa149d9f91b4955dca90030429b5988a9a41b39fdbdb8f883dd64112d97111888e81d683c09a950ab1ac4778bc90fc88956c7ab07409b0aebd5f980e762c6aa86b478de986a05ea4eb58f44934f165b5e419c946577b2e2d647f5bf0dbef8c456ccc53958d34aed34e1bd9a11a4e7e2256dd86327fa6f29cd590754420ea328b94a5d248359cc6c851e63aec1e21c7ffc28a172d82be6ead7d9256e23cf8a0d9a8bf31c7d02bf854c99a6fc07c1129bb81a37ae483331d95db167888dadbb27018fa9b144e6ec9d0f063a0f6d08d9455a6a3bb01b5d1387fea1f42ec97cd7144cbb0f570b2ee78852638f1f9b2143608a6f7927afe2bd7e6feeb22c0d0d135531176eb40f60d4015a2ba811af000d6c8595d409daa9100c5120e2e0b0281cf8627820204574492be191b1ad9db24dfa0449d4192c272789a2500b83c515bd2576f1f07aa5c1b916a78c6e8686bf8f555dbcbf3508905c08e3179d18fbb2ee6d52e6900056c0b52e2f864b544611470c6a81756ffefbeb7c9d35b7442c85116e7b883948ded276d81fabea9f4d54c86451be502a21d885a4a9156ff75020d83c78566ad9a0848c1e2656ee97038da58db9955842e60cda322a689915a6f47684d8c0a093f4c81f1bd044021c36f031a8d4e1c2e79dd4d57921f23aaff771ba9efb3f4397dda1fb15929a5d92fb9da30c935babc78d58f57fc117a8a3b5796ede2cab78e081a6ed319eca3a4a6b87e618463d437e7825c5a1ca8496c550e6863fbe4a093de72557246496908582d795861a777683c669c3b94505e3bf208a40f933cd96a60ead4eff8fc22501ed5cebe0ed3e91952bfd35678ff6007ebb5fef7183cefe3da8d8ba0ac26ef8c7c73133279671f47b38b2ed5ac6ba45bd16fddd5861324e498dbcc4f198a45ede1414683b233adae93cba355e75ed82ffd90b3b785075eb1aaa76689040b97b17b26830ca00b7c8745200b10abf4e1289b1c07b3b51aa5f03714cf0c3069502ca5404c5444fccafe365cee81b13f479e886299bbdd0b4b364db2f6e240f122708be9ec1209b85c4cecae61ecde190da835f795cd6296e3a624b29649d7c00ee181bb7fe126d89d984c5963c3bbbd60676da50be5db2b85b2b913eac8b1c0689b02b1cdd4dc54ec9fcca0c1cfe4621e366b5512f836ecb750fb6c782a9addf083334759371aa5e9355bfb601235791be596eaf22cfa848db2803068b12635d3c6225b944b209207b4cf398ed1059130065fdc29eb504d0e293110a11f70eddc8a3a28e6ad6184889745282237b011b9bb40ca18cf7b5ecb02e7c2c69e6d44fcf0f4b62ca353270a838b7155b42f257ce85c212250589aac320621c9771863f50c5a6cf5be24647f8fd23ed0a4c5dcc25d65903a8dad0920d594ff48f9a202f7329be4f518092c121985079f42900af6494617476c09437d57440458126b1da94d78e1c036f952987b01403879e444a2354ccc526c193bbe11461ac7c83a4c9e5fd11a8e17460f46baaba67a69e58eba555d6b43229e36b1506adc0f3288e72eee780bccd5996ca1bbed070614d6571797bd8dee835e53d0dd55465a95a536bd7673e1d0f662af232622ffecc9b8fefa4439c9b6c54c9c0c24749ba1b017bc15b54b71f37291c7264813e368faf07af42dc0bd9375a03cfc8d73545623ae3299b823d4cbcf52be973baf62768a6ea1f271c09d44a6ec94d6e196072b38fac80f9d5fcde1efb08b6a6a694dfac4d0691dce3a428f9cbd21b6759228b3cdebdb9810d2d7766d44218ca53bfb1e3a36cc09dae3ccd9ccb815f1aeaee22e1fa5dec0942ac0c3a0d9c28394c07c62744eb9722ee89b2513315fa79491052adb1414ad13661abcd71af0aaf558cabc70873e7127b041dc6edc9d82631b5545851a3ccff124f359c15512febb237f46f949b2bf82b3f0af316ac26abaa5fa07abeee269cc05450c9a2575b8e2df0a3f0799cb935ed4f38cbe7ea1ef9eae351bc7ea9a172c9432c45c24ad554808b52efa897a207860229908c8730964240c6c8b195699786565eaef7e9975e53320e330e6927555bf0ca61d54a51990912e0d817a58989e03417ac967d7e6bf958d6ff94b5612ffc3eded6d5e1f2d2e7a2e13144101c6ee5b6208e810f047e766e3beef0d18c1c7ed8ef49695814b575527409c27eac8f3bd43e991d1060baa30f3157ffea00d2e58dee08cfceec268b52f0c08e39b975445530cddd2f2ad34e940ac4f494923c8e1ffa54dcc8df8987c2bedabc92ecfbe0e31c8659c0ccdd09cf6779e9bf290e6b048972b945c89c6c57ee6673289ab39a3939b68751c063c922ed378f6d7c3e7986e584b7f32ccf34d3e26f8b471c615715eae8a6655cb174c446913be1d87b83bb6ad203c5308cb66d22e619bf24fa74ac2353fc2225edebc54ce54f55212cb71f511492a1b31621fd9efe6ca9e2ea95c2d103249534f892b5ea91a18de0bd9fbc563e7e12263719eaf9d734ac1134302c050aa5c56004081b8244f8996788a790b2b3ba7579bd5a913e0569ed0371ea0ae897e0a608e4def86854c750ec1577ac6a38787b079f5adcf23271e98520f9e144060adde7ebf732cd2fe06af8f7cf14180bc80492ffc982a96c84da9193352aa475724cbe5d24787582d5d2e22588ad8d1e7f6d58b29ca2fdab9e39a8683ffa0ef188b9ace1fecbfb01207164442353eb6bd02ba43a0535d9fe64f42f7b0206240d76fe37557b910ee0f651d3961601d2119b4ac0941244a8bafdc5f18ef510a0bb244be532038fc6b3adbdae578b767941d31c9e18c5caf344e348ed6fcdd0cad169a50f5bba5b613d7b55fcef699623794814c8246f7c77f21e418edd75dd7d273b718bb8960b1993b18a7c782894ee44599c047233bafd48b87294cdc28ec889519dc55199f5a8cdba289cc5a89f85a89d751d9d3317c5ad73e39f8df6d89c9e724ebec9f54655f7f4fc80d28ad615d32202616981688d65eff59d7fb989de139b9a06045ceb5e52be773c098e361446e18118383c67fefec0565f941a973ad1c4d249b6d1173a2c93594372ec33a44e11b1a94e72f19bb56c35443e032ad055b3702a40e3eb995f0325a79e9b7bb816b0da7fb77fb1cf16f6b1a2676f85bef454588df0618029774fbb78c3a73ab5c3d300b6b6fd1aa9bcf9aa1650547fe8dc7c4137e2171366c455617bef9af20de25c264d66c6ffbbabd505418c114ca9717de3694a3eaa6a6192dc02b165fe4376ed6bcb89aaeb5d895843e9754a08aba315ae48a4891b2d4fd44798a63a79ce5a7d97f0675307e4f49b88e14fc6ab7314694c486eb544ee2a098e7a4bfb6f48f48fbdc057700f97918439541b0ff07702cc5315ffd83b312451236c3bf30f590eaa34c2e2ffa5944a923cbddd26d18664f840131382330a45effbce1bcce53c34f5cf11658a8ee951dc92013cb35f110be3cbfe869ad331febc79058587f3531f7293bed1538cab1e10653f2e9c9e40398ed302d22db0d49991698669cce7360fcf5ca5ce3cef00c994b95d45d97d9222f1eba1457924253df0dd8973a56b234ed5c5f95d28907f9793062980bd5e11b3dbf1357c388b5b2f653e3f5efc01c1dc8bcb9479f1649f0645cc62ca70df6b3006b617f33b77b20f74086312d8a644182a2885fcbe4bbb9983ccd3712c2740252799cdd0fa19d7ba9ac71e61f8e6ea16cffd4b088a5008a9d9809b24436a724a3ad32ade98c8475b674be355c17d191a5a389e331238117bd48e4b281e1c4333d520f687335074fedf218216315aa80485724c542c315bb450c5f89cc0d5e238a96d44d49a3683875c535bbe27550dded8209b866964cd83a2126a6018868109da909060a5eb34fa7e68596878af9a2284fc5c187713683cca8a664d7be0729f469bba9f216b82c69bf89d0a86245e6525deef9177a3dea919df1994a3c68c5ac6f234650ea79ae89f949a4eff2602a3fbc1695823df60a8bd449e58b33372d6009a4e5533b097eb98a8bf832430bf912c0109c20ae2c80d22ef5b3492a03dfa508bd02e364f22919e93a4e679e953f662e156883147255384dc628ed6190d110bca3ee6ff43b9315f3df503697ce195a986d9417450eee553ae3c3ec70a368c5964c79ba281b65e37c3bfd1f485eead1f47de46819060e57a8cfe4586232418acd42b855e3444c95b06134bb87002e41d5c106bd29c1882ecfe52a94689218413e410092cdc6fa725c7194adfaa2c5eb61127c9e05944d9e309c8bbd8267a0b2e811be1d2b1af914ec128cb08a2aa07f5b25777238ff89aaf00f1b13ff3232c24c2dfcd1577e264eb6bb911f81e4a32250d9d6c14c71e28c31ec20b75c58e5a63151b8a4213621bb94a6938bbd55423e277b8b9b35a5ebc3939b73aa7df57c02def32bb590e615bfa144103ed78ef54f215a667a4d8800c0e2a7b11acb2a63e3f2c088a468238dd70e007733b61bf9ed3139368d39afd0e6006422f73749bc60e3318e0913043dd115462a478b7e5febaad7d5328cfb941529f0f5d9de462f4f5542a76e39b40efc605ce8612b5cb57a621025db5a44585b73e2564cdaa6b72ce02b684118351e3767bf9bf03612b2cc5a3a54d3483b8f184b081e48144370761c196e735f7ef0a5943fc32fbb52fafaad4a55fda54a71c1175492abaaa32479cd7ab1f1044b05b79f5e8d597209877953dc35db4849383bcbf1646ec0384f78eae1917bfaa10c61ef6f1073b6e858d1f0a44dc7235b6c3c61004e7939f1fc3135861e1386838b2557f9bde39e7964016dc89069a9594f3e959a4de6e6d202c25a923394f96cc2a0597b4d1167cfa9609597eb9cd3717c56f850cabf1d370322a2088763cff3a51a82d66f9d05788f2df9fbf2d41d3aa3f1cb6f073602bdd8c80e7b53a7294dd27b583fdfdf30d40b289f3bc5866cbb4e05cb20f5a71f1da1ec77fc435383c2098b4e32d865fd774ef81a57a5ee997672df4ab825a15916a4a5a7580e7b871d54b1c1c243a8a536dcbf90b8fd66a297d208f01fd757315ea7777e56cff263a4f79a8de73b0d708f92ba0e9cae5ecfc07321ba1f5920161e0ba2f2456eeda2b496fe7ab29e28a19819bb39394d183136195ecbb93550b74c3614f6c8b344a9ae74853c189d31a340886eef9ad7b472c423238ca6e93f81a7e152da2a92e0068154c1f00da8b71524002dc751a2dd523e5cfef51f58f390c4ba58ae1d7176c88c9d31bf650369da5c183f92f00dd3371844fc060a050d79357ee3f127a195772c6776d116790d67070609b333a41a74d9de72a754896eeaea8565484b994aceb47113fe74c6c6942a3dab3b6374688fe50c0ad2d3ef37ca41d774301780c6cbe458889488743102f28fa4b16088cc67814c6c6c573156a123be172138498f8595da004b9fbc6bdffb153da25e9317a977ec44e9fd9760677759dc5582593374be299d4176f0185caafb866d32bd40a888c4ca4d97288bf9e9e5b93542fc66d99229e405e4f7b9213c201f44feb4e564e0b5d0e724bf0007af94249ee32657059a41523c586920817d19f636dfc926025d642aacfec3de4c317f583e4d65ad11185c54dff32dbae0970761ab5d45de1da1455765fe15f65c02e78e7597c392449189f23b9b24d9dc39a033e0aa2b2f276f9d1e761f6d2a1159d10053bb32475f2f3993d7bcde65e7ddf3c60803cdf2aeec9660776ed493056090bae94a95477c902a5f381c0cccea55d7222453a25a570ba9a609ebc217a373508a3f162c134aad15c880f0abc4dbc1edd5e966f9f73aa34c16dc388e182885130483a61c9504dce22321e876096ff76544de37e2aed3ee32307347e5853ec1869398ce4cbd087a991146eb638387a3168c7cf20b4fdd20492855c7f7a3b19c86de60bf108e40ca755d8a2b16fc22101a63edba7f755090cdec8c7e434438201651c09ddfb2f5d7833d4bbc394262fab4290eaa295e2acbd7204e4bedf8cee3f4fe9c74493e626925dec15be876d5b79a86aa96070ea7e579434515d78f034188692f6d08c658eb3ab6f5065cd4e25e014da6fe5feba05a07d6304983da72bf07c12d93907e45272bb034326c6aa0cdf12150b0d0449d5009098165c1ec185c4698c28a65094c74054fcd773f7055434272ec7c472ebba6302ed36c992b26dc7476f6017b21c58ced29eef1f6b67b7b13d581ad4bf6f5878eeffbc9edf9c9f1df84a168a1ef8c9d73c5810910e3c6c3931610622e4306b93f584d7ee734efd06a8bdacaeddca4cdae0f99d65f1ac533931f98062d2da5dfadb7aba8e5a2c721e10104d84777f2db1227dc415adcecfbc398e0dc1c0e3971db26f6cf3b71bb428d44f1d138247ffb929a59b9d4cbdc3d0fa2dfa8033f19b39f1acea9e7bc93235f5d3a8fdacb45a69deee8385888213f0d270e4d9cc339cb7f053cc0e593c0a8af6eff9297d13e8f3bf94a228d6d5618813481e1d0d2dc74a2d0e792e6d1523ab0dda58d090925f7532025ac37381475d49977c7a7b97abd5ab36eeda75c5ef66521e45225d01d8ec246448b96c7b63a23412f58f3e31f52f4cf503fb57ba7dc86388e654db87d75dfd03a63cef9883d371028b3a4f2d4111c4a9e3f0b90db9c283cc5aa7992a602bcd677b9f2fc8ca6657d92a61f87f22c50d937c85cece46b433f42f9b593ad16788fcf7b9ee7b220abdbcfe083a2e03ca969a2b1c762f4f3b85f865b3ec93376e421338acae8d0b9df940f1e65407b57b9e669d19b17f2199890a6af3f9581214e133e503dc9fc1e87e2d6716ce312facdb7dd0c1550afd974ef7fbb865b5872faa704dc72740fa9e929d0068359a45b78b3a1910b7d4604e9a8a79795b62aac1d7789dfdc1f7406f6d61bf311dd8729f4a09ca8e0cbbf68dcec65a81e36f9348f5ba417786a6d8cf97c442f5fe56965cc442944e8fa644550ec7ac9752585869df21017df39b2e8590a1e842f92a0013e0c619c4306c0fd68699964f66751c19ef17cbbacad6caeed6f7e568b55e42c9150e997648db8af31cc9f46bd05c17890e993b4ce9e372de63bed3a9df18269391ddde81d4a5718a07bb510244a2b286c07df865a63c8a8dfb14206388ccb30a8e2825e8c5f2a2b6c934993fd3c3fe1327d9330404980908b3c22bff72b6c610642de5dfb53b33449462d12a7e6e3531e962dd5aef428cdd9ee8785990d0df641d86c840afb81e78348129e0bbb6c8326a0882284bc0370aa031afebec41f70d6085140940bda09bfa0def992b7d39e711a3266e3a71fd3a3253c5a439271c53e7095d2cba8e489e49437191442ec5caa8b64c2b0e1ba001376e37614f30a4c963b34e19b45f7dfd47e26451814d456469b11b132e0394e38613a74dbd4960a186fded771ba449e1795ef2a0b687fda50b1043e9b303403bafbbfe2fd70a792d4239c6240add797fd8fb36e2d628a86f6aabae9662b3758bd6e4ede97f1faf2495b67d9e464ad637277fcaa6d5f25e12a4a4f0260ebb8425ff17c7f5af7999a9df1607b003a01a64c656823bacc4626865d578be76d83da2fcf2b49f0c07850f1abdafc00dee54e55c7e4acb2d3215c2cca6d2d82dc04b1f8c69120f988ee1a5501dae9bb9dcaa558dbbb0a6aa724f0dc98c80cb8a97c7ca7366067ecdc51cb45787ff01a6851e76994d7b99a6005b823284f041621acc3a2b48739d555f9759430b9616003ec63cdee19499ed19705df9818c7f4d6a7731a3317ef707655544445773fac11bf7f4b80041b02c1d8fd08d41eeaff5e8a86da6a0b8198e82c71c537e0cb8ee0d7a7f4ad8735200df348bc5eddde8e7aec1805863493d4e4aa0bc542efbc38837ea55d14be82bb248c90e58c6435c720a308eb7b95b6a6b64f585eaeb5854ab63be6c69c94f3355722e20fb6fe255938c9674ae7591c40e2cf9d5cd07c84f46340108e26ccea0be11d192054d55e053929edfff88bba593bb09b6e0186fe8b43558e86714a4f5f9a1b295de67ae4be84255c60b479ba4ba3d6a6cf8972b6b190a59c53a79090a3215ae129aad656ccee99b52fa827b003d7fb910c3e43d5eafe343338d557c6b0fe029b97c325171b041b2061e34f6cc35b9c4cacc1861d3449404188b06e1449a5052ae7aa7bf7a6b864008a828b88bb04f63b4624390ea100f63e8c46b5db92672654075f2fc2dc6bf8a319f90fff51b5c0ef19d7f9e00b2251fdd6db342f7a8577a699f4d6d54f92ef308562eb04b8cdc7c69ec4f663ae7fab8b2b40ae7c60219b952f36c50cd94aa4739c9aceaf27e98660d6494e601763560d014e7276f73b764d2c672d581c2a2e850bff9443b0dbcebe76203e5eb99a8c97793444c0103b6873b4cd835c9076667014eaeecb9dd8b3f1d0dea86932b8815b3f4d66ae9997d5733791c274d1790ee3dc495360591190afead67aff0c5b927c6c939597419f7afcdad576d20d794ca4b3c73f6a0fe263844d878107a116239c4f95c2b4070820ed36a5105d120b15138d2dd107b399651a4bfd4046891ca18d122506dacb83a3d8ce58118472a3dd5bdccb38756c3e53e816fe3ec42f1b67eb58e24e15426380c98ec8d1b3dafc3386551296a7176c48d07fa046059951b7f058b2b862894315a3c450981cab87c26b930983f678f6047047c46e0151ccc1464557e18b8f8c8facbf8f8e7982650981010bb4901f1c9c0e61ccc9b10eb9321d248143f7dc487d9703735ab4805df31e95cd878c67da8b1eb062be001543715aa4d3c3b2ba6cab6e4cde4e309e038d9c3df278c1e4fee86c966341b35ae1663d029a10ac62c06b0699e4e273e891fb4b4eb41f4635a8ecfd9bb2d78445e5ab42620bcdd01391b73078b1a7d38b8bd8090ef39ac764b04291bfe30365992ae3089ffd9194a703208b40399037c12f1f68bed3f9bc2a135f1e377e670e69c8b5caf30fb054df086b77a635502ab46444d57a4014449206c22a284dfa75fa1cf5349aa1bbe392f3f808ba1f402ef10ff2683b62782f400e72e41d10ae2a0413a7aaa95f5bb903b527e48a86cb6d267114003549032eb296ad2bb8ba1fdf27c3df8b9b08a515880ad65f8a835713ce5cb0c2be0e0884a069e05fe9951516224669a9fcd364e63b1a91af5ecfdd80285ba0e590ba83a21af4e074e32de33e43661773e26c0ab233d4a1aeab73882dbc354d9d379fccf63f80fd848bed6be0c1724f214d28cd2eb283f600c0f7a5cb85c17ce33b214d9f482b3608cbe9d09fff2e3559991955299ec769445d8a41d691b908f28fc286d503e6962af48adf59f08a8f1e35df8b1ced3d11eca46c1c4b0913ee422dfed09e801721151811fb17d428bbf445ed31cb455d4176f1a223b6a4272709a6e822c09c977564e6c1fa54c836c9b2b2a4c3f194d63a749b567814ef741c795b851b60f6d1a1c87a753150141a6237bcd7e0a31865986c640f57046081aaa4855228beeb61dbb8cb7535433dcdab9041c76112ad98f48211f7b061051172732385e0017ec332b7e4407d3a7c29652592b361a7f4b18502ec7b88fcccf483b3f91d6023b079ba46ee7406a2de710ad4d242b577e118c16626cff8434a4b58915ca50415a4b37b24d73fed1da1cac72d03c5a9b8429744743fa699bdfd1da7512e11e06e4365a534c9843e6abd11aa4b4f217bc9ad1daac55448d458dd988ae3e268f395ac3d2502153ed23e511caf7851504096c34f39fd6ee679933eb0b8b2b95111d4bb446c90c5173f3980a750a8f150cea03ca1b78db358f217473b1f26cfc2f4787d6a096a8326530c1eb686a6a3db6469ae82e0b1b5a2bb044f25becb8ca36ad6d605705ff8aa26a57ab50fe94bbf4d6b0143ea149085cc40625c90f156b09e24a0fb4e6d92509c7144b219f35beddc58908c149d01a79a5464c979f0e0cad15c45a6c63614e165a4b61897e2afd23912631165a238e22635600e039e39d5ba22acb58e4a3b58e869727db9e179171dae4f49622610f19aa426b126ceb489fc8da70f89b13b6b28e73f339047c19dfb953cedefaff081427b4a672bb4da6d8e36cb36ee786fb03aa6a021b6f1b5a5bf8c283519b8bd3a854a2008306686d663e05ca365ca9e21cab37df0aedadc69b34252d005fce4ae0b3666ed44cabc9934ecf4d5c19cf5ae35e69aa85ae542d5508b92caeaff0df56a7e6d725a88267cddb9c673feaadf1ae7793aaf2152166f4aca46731c1f4b91814010fde79d65a185261f5ac0d6d91ffd1b33686b8496c928f676d5fb3ceab724977ecc893d0035a94f40db06fd4ed74ed5a3b1f67993a3139f84f269f85c7516f18aa2c9c51153350f481dc893c024e8e440babc9e7d05a7931668a2e06bc5498ad68c51b30bf3a4ed562520be4799fdd1b6003637a18777b04f72d510fdc5afd2107096672fcc8de0f14a4221f26a3804b17ba0fbc65b937169b37cad5ed8ed3013c1692a73dd26a2078c26d51a915579a96428d0b457a4229c3f5c14194c59822a469d85ee81b37935f6842d945bc8a3baa086e60ccdb08118339273bd25868757f71d47d901cce6495f62a430d6f4f198b9d01e0d2345f284c495318473b3c0314589ef1955691502ea2210b1d1151c92d73d9826c26922d3da465c52614ed4566827aaec164e266ebbfa33f0af55644f54b9bc76bebac3a1c209eabb691dfa58345f2490b0330aeeb3e294b8fd36cb476d39b0e7e9a18d0f86c54a232cd5b98b12fb99f73b319a6582cd97a542df95aa8e8363f31b2647591ddfed90c8b44b264530ea93a9cf64f2ae8bc4a7a330c475625c642112392489b60da1ed7f4826124ff9273a5dd5bb2b6e943560d5530b41898faf2baf1d37475854d90e14920aac08b0db0f14002cb8ad5c60627775a50b189204f92354d292c117e43086a36587e36877ab5e76a2bcd2f5f58ad42731c5aac4e9305128bba8f2e43b36a8f5cf4095a81a91f42fd6e6cbb2842b863cd480b658f4e1120846bb1e3578433acf270b02e0630d4af50cbdf1b4c216b84bd3e37718202e60eb0422596c8815c42f4742be11bc77a0baf991db9691b8a6bece9ac107b8267a26c872e19348e322be375f70e577757e8bd1dac629fc8e91e9d05b086415b1982fe6f58c3878a9c18ca8f88543d4151c132ed2df57e2b6746c70e89e3102775cd99e5e804ef7924ba9b0f0995292c2667cb89d72b069b1a18d92f9dfbe5481bc9245053efd31fd1f37508dae36d6f7cb113fba55962fb451af4b4cefccea985ebae40a40295bf54e018daf5a9e2a687601cad5aa578bc90c9c1b04ae877ebe84f56f78f818f4153bc86b2a15ff664975ec4c4aef73ba9c79fe9a3ce69f065867c4d25063dd754d46cdb620bf0e1dbfa88ebaf59bf2a2760fab0a918927cd061ec761bbf312ec132aa2e323b19c336d49b70f563a21ef4bc4da46cf2442ea4cdd59457f167a75232aef08b61dba0a1740ee18b8d17b637019ad7b110a1c8c7c491cd5e9709aa99b32bdb83d30888040615974c7a3865e4a2296da316cfb0278f6e015a7b73bf04b0ce43313fb70afbd9f8ec28d24c169acda6e4040db6eb85706b8e9eff73d8eefa9a68a33c276772aea99729bfeb904927f63e75d04adc9a41e4d658e91b04461a86a7ef58722ed54dfa1378fcedec08f60a136b1845a39a258fb732e0cec8c3cd1cc19d051b4ea6af2e4cce1530be1cafe2ef738deb4d6e1cc24491c2786c2ad76e8d6349f36e1eb8c9c7a705ddfe28cec1fd0a063d92ed2bd3fc8091017b5d061eb7c9bf7206db04c7aee77bb6bc3043a4d6b7c08de129a204896c314c4b9cf08027bbcb54e77b70e850f6da1796320645d6bf4844809ca56ccb489027214f0829579f5031452b669f2a4d99b822d450787022d313c2d0f379e4f995856677703390aef2a4865a757cd20cd74785c4f5197c19c9d11cadbfc6601070f59cdd9d6048bbfeeee0cddc73d9759468e0f14ce6b8acfb0b19ca390a362d012f3998a97bd582ce77d8f5635620196b12c07332918cf219f5189e278849a31c83bf56c0c97f893f608e211c13211c10601bc428cb91b0d608228117dc25513a420302864c143bc43d9c66d1d039fc0ef7845b2d41dbc23df3c16c91ba3cb827b05e13f6c20c9dbd316366a40ffd51b06668641181316b6ecff61c782784fecdfbd49f5e96107fb9f1d5b9aa25eaeef0d71a35fe96a9891813baa5f9bb6e180f1152b20cbbc418879b550a90583ef138496f5636c3d06c3065254c21178766ebfd665512917e6a48d21d4a104fdc198d5aea7bba60bf2a387d12731770ad3bd600b4e8d7081426d7f5ae6a59c05388301e5567fb1bdff1d6b2fe793d3840a2464171369a8c618a32138e4b585b2680eadfdd16b7498d914e50ea3a48380b0dbde00849683af14f220a410f991bc3314d4224db9e7aee16cff75b9f10d816113553e6d5aa754923e83bcc24a8f809584c00963239ffa63230887774cb1ae38a00c907801670dbbf73bea337b8a409bb885dc8c190778afd4715c5e696456e1cf084511a82275d1f0235c0d83bb6d32a3da992c62574213f6aae394917023c6e34bd1889193cc2ffaef1d309d1e1e3c6034206da7fe0adfaf752238cba2c21e6a94f401f05dd646fe059f2e11d748da9b27faefff1ccaf5c1e604960b66f68774b64370a2af69e6257172150efa9e16925b8a14b5736849cdaaed1c5325831108c302f8f2eea5bf5bc10746144bdaa6d284e5dfc1d9a892665b4b708d9a8491878092c79088a72a8642e4e1cfb2ed978660ac68730950cdb4e5de1821750d058005a9d37c6e3fe9f286b569d89391250a857eb3efef7156611bb9764803d44bfd4d505bc399d44953e2ff5efbbaf629532eae8b822a930f14238619cc6dc292f9aca6c408d17153afd142e6e46e9267fd1bf1656ad1bd10ff30931c38d94ecdcaa9d8fdcb9a3e01248e341bbb60473d24deedc485a6efe5934596bb45ac9194ab00bf60cc4c3ca088c88a1e53120f05cc04d207d284f1bcbdf5c3927c162ebcd31add7d295e68d412051c722f98bc57b7e06af86dc28b5c4fd80901e5c08029a934d5a58300edd4e07487fb045d04d939ba6033c200b461718099aa4d6a342f10970c10b414916602da85208b55c4cea64412ed3007510a09bd40353f2de4f90286becacb8bc50ce06fe469b88580f53ee57b7bddbda967b6f29a50cbf0532059c0546463cae0f239bb225c51045e4ecdc9f1ec51d398a48355c1a612e8e56710a868a15ae4e520b42c5209f12491cf74512e6c791220dbca20e1f4b37770acd060dbe197554c53616ee958a38221f20d7aa4807ac48a6580449519eebf503c51b912fadd83756245ade5811f55ab87e4c45bb245e90fb83484491866866abaf889e1288bb07579cb1566c51f6e5e566b540acb7054a8d29dd24a4228ed0de8f6b37a453541b0a31a4cf0dda2b420511d143449b0b341fb122dbd2e571e51a91a25312fb906e088df8293e09e9b83142215c65df9e511062921e666d25d09ac45caea58fab2293149992a804c94fa8c8f5538c4ba1e31e4911c215d2471cb20717a418d6c3441b0f5ab18798fbb4acba2abe49b933943648452721b39f1828745c351421dc157d327b45a52045a01e4d6d6e08341749ac28b62c5a5d6d49c9286f20b948848a7e8a5d2774dca31321f8fa8a7b6e8e20ee4e0fa291b6e2126d895890a50b73e5e249157994c52c2418427d3f7f2674dc33132188f421d9d36ad3203d6dda03c82d2fadacac2804b6b6e55255b5b928ca5076243e734f0425f5bcd33768a8ef37f7defbe1101006a46603047a45a440553c1c084a070562aa398186764c209e9d0d34b3f3c3b7f343c7639a30fc23908e8e5459f1b2d1986304fb63a6b3fe78e5a83f38c1136df1ab755b684f3a1f361ed4874ce7f4c1ea3159b43e43383e3c3b3d486886c33fb21dbf3c6f47aa9cb160ef4f8e1fb31faca7f767e8fdd9797f60f27ce85e1f36950c7882157cf25132d5d767a8e7d787c7070649cf5b48e721477be06c7a6eec983d3f300b0b2f3a656cfeb889ce39e729cb8ab241990d1ebf8e6c8b373c45da3c1ebcb61dbe8e1bcfcba3d691473c156b2a2e20ba123cd9a74dc1bf76de53b08bdf9e1b1dbf3d70c59e2e8ef5d4916952c76f0f10eed9e96ac76f4f4ccfbf8ac06c08c85f922d6b66a9c786161990b9d7666dc6bc31418da01dbf3a6a48e63acc39e79cf3efe823eed84dd162bdd2911bec8ea257f6f0c47a9ba7c975caba6c1378a6ac8b46d5a0caf1d627116802c2af0e57df6c09191ff3677cc41f64f7abeafeedb2e0a0e201e660d47aeda6310495a13e6615e3abd384225521b268edd6b467925c6b1ab96c2db0e357c744776bb4c18e5f1d111d694ce9f8cdd1cba1ebc8e1ded339cd38d5e73651b03ea220d7de66774d3936eeb0b0f02fb3ce794c1330c605cad1f0794c4dcc08748fc50db287c1aef897db8e5f9cb3217089137683b3a4c1c1b239e9f8c5519eb51dd0bdcd8da63c4d795412f148cb3b7e7188da76f8c38e5f1c9a7ea643c529ed062dca90735ec4da1c3aec1bbd9fd6668a737e9a5bb7edf06fe03af2f8f4e7b6a4ce98b386131c3ee75d3fc00e2650eda4bc986cd18edf1b17473d284fcf79737363de84a0d94a6a8cd8bc367a33dc681a755e9ba9fe1dbf3636fa858e5f9bad8e54396369549d9242fac04fddd44df706dca2827e17dc8f1b003ffdd48d41fac07f79007ea3916f60346ed0f36e91e89f6b9699817e39f526a1e69cb9e11787afeb3727a96b15f8df929c57a062adc200eec5b193e992d97a65b0360080102c461ae30c96c391139b734bb209a1f7f2445457038f2808afce04281023945b71098815a739a52941ac281ea018fb81622c2e39c3337091b1e45a2493a9e69224a7e0924b923a8a72122ca42fcf2f093703a3dd14f14a96487270c09b0ca1e8e783a25cb34cc210b1c06a4afc4657231be9ad9a371fa5538a3b29ada4a48d9e11a7165a475a74489358fec36f36b14cf64812a0c826b922d952d4972631ca71f11424e5e63502241d5f345d772a5eaa8ea2794c5ec901140d8bf016a6b34b6a252f1469261bc90e8ae2c8924761c9cc46220986d97efc8dd0599156a44a6be9450a2f6d86998e15d9100c0e5448d3ce4092af1e73c92caeb197177a5e7535667d256a207514c563c26dd9cc0cd729a0082cd995744a8e30405156d267a785d850674292c444a09268918b23c097a7197743b6ce60fe72e02813c283c65f18179e8c137ad704824e8e0efb21a08f1370d3fd26e409e82308d628d6ee3fb18937c6287973606161312e5be4ca87e6e38ddbae47a858bf2da83a7e5b30bd517eaf8ba1b7859fdddbc2ed6d11a63fa778d0dd7c164a807cc1dd05fa38a762ad5de46abafc83a9d3a9b37ef2fcd4e2770aae033fb739841564dddf289e7eae67366a3dd2f4c08d626dfa48e0f9ead9e296b3d5e999ae40e2d9db13bc92b2c0cfac4b0ad04d53feea099cf30d9357c037fa26ce5b9360146bcdcf67c6196bc62de38c15f4aaba5a055df5116f41c084fc6a0b24e2de616f30815027a07854b88e5cc1787ccd7a08baab3e6e2356bbdd552525b69c8832345ee50f4cbe910788800979d5ec04703f3f3dd3471f7866530bf9bf2bc63a72359fe4baf9ad39ec66faa8cba9b316eedc79ba9ebc4cd52bdb04fe07fed2734e53e4f9e17441ac7d7f0ff575c7af4d930d04ee0dcc664a7ce99c68aedd74d537e2165be5d63815afe907d5340b5f15be4a0c0a892eb857c0345fe80adf7ca12b140a858f0a1f7da19beae3bf74f581cf1283df61bffad2814410c39ea244f345cdda0d456fe517061f77164551f47ce2078daa287aa227abe293ffc9b22e52f3f4f357d03db3acea7e78966559e2aaa2af6a6dc29e905fc8ba983cfba65ed119eb9c49c82c4a34f99465f50eab51f4eeadd63b1a886d5dae47e4cc5156a4e6c9e09eb8c816d9667745591f0575534efe7c96e50206f6d1d73b28e7ec9b98fcca9e404466f63564c8af7217e5831aa5bd00dc32219e10452076d047f357dafc3aeb1d9347ff24e66f89ea6be2197c9393bb27c63add1c4ca1ab7e481f7de10b856e0af0cfcf6fb62ccafe7316ad410c390b5ed4d7372350960435c822823e9ae819063bbfa62d27bbaed2f1eb74d59ba2ba013a7e99926ef4b5b7af59a0f769e855cabdcaa957b9f32abb56b90d67a26f56b91cd4da5d7510dfac71ab9c5e09721dd4fbd5f603b059fd5c3f0fc2e9e76b5e47aea059e2870c3f83ae3106ad3386b81c02ff8cd41a5e67780d2e5277e12ad7f305a1ca75d65a73f783f033bcce5aa341e49af741ff0cf607f5cd0a875ce976907d54b8ca75f4397739df7c73a1fb21788173ce6e9dc15d831ae5d1e75aebcfaf6ff44a906591e82357b9cebf65db0c2f6cf9c90e6b77d61a70fdfa3f045d645eb506fb7798e6c1611bd6f7eb9b9505ca40cc441da58ffb51887a4aafb46ad72b5cd7eca3c4fc18d0efb0d90bfb511336fd6c9273664dd6b8bd065fbff0f5b0afee87e0f3671f0962d88569cab2423668f2dc8405ea481d954f0cf11a07020902fb21f54e67dd02ccb420863d3f674160bf9d8eae71acbbc6e504a0bf6a0e62a86fd638391da5a7f4f1d43a6a8ddb701a0851b156b1317d24f2003d7399ae7fd5dfbe50f3bbe466b4d162ccae4293dc57dca273561a75cc77694cc3e1331430803940601fa06f13b79da1c0011158504881031ed80065a857ccfec1b0e377c945df1dbf4b2cfa998a8ea247e426010ab2a453bc5b92290a41229537f3c3edf16622ad3837a6b45cb272d35ab8424c39492291201b152274a89829fef4b94c7a6e1248b1062e6d0d4759d1889718964b26e54239551d11ff4e14eb94487429d5bc296141c41d1284288e040e17edacf802ac38228be8f702ebc9a543729d525c25f5cc24dd1495fcad3032764725e786b5d5b001f3f2d82ac28821054542c96b28eac70d29670e2906089feb4248516bae98a483b8e4c60eb1182e37a7aaa813553cb244dc2b72659470e15ad821d24208629124c514438ee85c203b70a81587c8de2e57c55571cd4571a989c668c8448ba3117747346e7e4af65cb03a17058f620cb6229a99dbf3e5a69051cc314544a2cc6b410f851b288aa2a999f5f475c24985d75c1beeca8bd251f6cc61cd20c093064a13786d5c3e6220e1809223582e905e1e3e4b9ee74a2e19502b8ed8baf08a4000c38708b4afb0175a297762d25a644c48c2b2eaa2d67c48d4488bda79334a9b5660cb8392f2233b723b6186520c10a39fb8bc2894e9379b0f15b97d8dd0667488c4d570090196e451d3676494755a4d944fecccf958536223a4040fa9caebfbf9fba1d903737161c9c882cceb85981fdfb28388f7adc889b12827302414e32de993eb889f0a09acb9c8b9536a31e43482913b311d76304a3874702fb513258d2eb9785213371f18a49a1a20da43d3fbcbd13763a2a7841cddde138d9e2f1b0440daaf578f900438fe8a2879ee7ab8e1f72cb94c6b6bb1a4c62b6582b79426dfb98b8aeca2b5ec828363274ecf4a4846969c138f0d0c56de0d5813b005a6fd069d84a51d58abbbe3199206276447030c2c272ae60f6b09c35c5aa5477c895295dc91a6af9c97242d258db63e19265044a146e632a7a3a790349939e79cef76d8a2e07d728e53b365e1c81217673c706e34e11c7424c4fca658c09475e9c84a9d73deb6ed0795b8726c5e9401c0a373a603c791177d02a8a11461048990a4aaaea7a5f33ccf353d5272a3d503ad6aea46043fa69f9b1b3523992e98fd76119970a2161b4921222d2c256073474670dd88190d31c275d7cf778984f3a1df388e326f34885405798b94a13921e0a67cecdc6c84a9091d6396540ffb6fc9cb120ae25ae0d7f12359b8d25aeb9c73d63a6bad172044be3001bb3a2e018f999580bff23a2e01eb01e9780eabe3df917ceb9bbbc26e4f1de378c365551d9399758c43c75b921c965909a058bd81b003849cf3e442a407af234c569680323d376e002a8e43024053766262004a24a88067c0c4f415f4e7ae5d640a6e982a42a40ab44e8256deba6ad7b1da0e0a912d38e33d4a404b74ec72a6e8b8e3c76c0450a034cd652799e6364d739b7b9be6dedb34cd6de63221b2ef181dbfc956c72d3901990f592d83640a6c190956f1d995d1189aab636166a777828e07d9d9cd03b682a41ca8efbdb74ef40c6a352a7eaaae91ab0a32b2bcb6527cbe5b37af5689a4d33ccdd37c6718edf5ddb21c8d79ebd188b3fcba49c7af56cd12a31019595e28b62bc11d9b446f1c96034306ced052f86531f9a0e1483bb246374494776766a2f19fe53a8cb1229d4ca530cda1699ee7e3997ebe5922b88c2432ec54ab94fb782d69d9f841a2a68bf7e8e4f0d3a369a1c486921f32dcb0ea50ce63a4e33c2949335431c65e152f5e2c201c6e901d01d93a13d2da1dbf587359dbad0d47501f1858961491201dae8496d8f584050959d905ad8c9c70126e1a3a7eb1cefadaf18bd5d5c29bf18d4dbfc6373563dc7bc5f65eb9bd56633c7a94808be0afb9d61ff47a1ae34cb916bf69be51474b8a07fde8af803f1005b704dfe4afc20cba7e733dc12dea9bfabfa74407744ee4a0e3ad9e12f15fe7c4140f2374fd238cb0b74fd513d5950ae67cc44631ee48b9888e1c765dd5d5f59b6f15968a95a975fe539bab06fd7c4d9426e10db8234d3c5095c01b70475de4495c51635aab41c727e859af1af4bc350ce38d1a54343188c490ebd8afe317c64ecf1d08cc0fcc2be8d4041d99923be894ecba401f814117c4b0e3c7bfbed1ab05e2073e4ec1cf3ff829e6eb6607e2fcc0578344e358f0f1af0f62eda9ca895b17e823f039500da604831f4cdf18a345aa60e5a88ca557b672975e09cb63f92c479dcfba48e37a9ee7b9aa7bc5a60acbb22ccbb2b9b3cf591258766d51861be89520664dfefcacf2018b08fa78be90d80102c4d4aa151283cfaeeb1a7cf5390f06553e6009021fd540e09f41adb23887ef0bbf772a4fca4865ac5a31cdad43d0fd437e8c57f223cd3f31d61b6bcd7a306160320c8c5ea24a8df5fcd8a85f29b6aeb58bb1ace5cf1fb9f6e301d03f5114a8d3d43481b91bb9b692c2eaf875d1d591c7d7c553efda44f1de3b1f8fc709e00f26eb726e0fbb7d93a7113efb994db140fb269f42eba6e0a67b838b0bdaf7e33f17137047dd02c80433ff33e79c733e41430737f8b5d65aebcdbaf0aefea6da547474db645dd0aebe6aae26918ece344fd6e53c33fef33c5d4c14453912f3f1a990b22ebaabdf019216f8c107b22eb9abaf0651347dd5453fed28aa9aac9bf5b0ba1823407599fa238ffdc8821238e8e9bb708072d05397adbb4853a333305f54bdce9c30b945cf6f46e116f3b37b72d051d81c4e18bc417f9ab6fbbfe38edc27ce305147b4363ff284d9301801393f6f3fe5916d3ad4f0f8550d440e7883fe0c4407e00dfad3ada28fda037cd4afd16ee66c12d1ee01edfb1d304203708bc9eb1701b7b427afbf05b75c78fd20e0160c28af9f036e01befe15dc127cfd0cf800b7acafdf830528e0370714b60a6e49bf2bc323f1d70819be9b68782171e986e5011ada130d8ff218882cb41df817882c0c3bd05dd2e0a67bc30966407e359f67cf40ccd0330350c88fe142fe935668d2f3b3edee2bdaf30779cf0f4c4d940bf7770d19881c834eeb54f355937801deb699bd00fae79b29a06eeaa67bc35fcfac4bee262fa6ce38156a13cc0217b5ef77514df25fc7ac8bda751e6249bb2a96244625f694c5927afe1405635d798a3fe9211a409bc6cceb67be76afdcdb860a3bc07beac8f5a893828ca9e397ec4447e68ec976cc3ce6d791e9585ec7701d69a667c7ef585bcf6fee31b69eedd40e820055270ee3051e82533583c8dc8e8f34fc56b4031395a45a6362c1340c33abbf18be22312a3086d889f9b851f103d675e16be94451144d4d1445d13357b540f98a73abc8c15f108513a30ded865d5b8714635a693855422ac28abc344099b20733ec4a602d797c7c40add059faba63a154d385de99e78ffc8264b6f396c3472c058c1532ca27b431b2be28962f9b74ea749e67eaf4db6befa2cc5584a24c1180221254d30211974f0953257888b24228b10afb3abb829cb9ea3c43503bbff8c8828c88e1fdc9806fe78e84121c12155892194bac72ea84ca68be7435785160756a2a8e98b448982599a55803a3bbb0d5b46d94b822b2646feac8006ee4c839a3e1456342a8b5b4c55e30a12f462efa9e96868492652d633a267ac01c29bbc2762e6431857175f4ad11dccb97cb60425fb9648033bd9c3520426d71332bf8d2f8443801411186a465699b651a9315f9bd6d6a6865332d50fad2110115d4f58482c564a24b2b0d5dbcf2ea0aa6c9d38b6acd4f6d2364243569dd40dae211e7276326c411e5178be255fe409d91d791dcd4d31a108c144596476a6c865e9a137c239d5acb3cc9e94e0b4993274a7a177114f52aeb68aba5341492113143c0b6aa70289d5ef8e051a3c64d9712146e951ae7f84ad8d40dfda8f1b4bd608ae959a3a90a8bbac0ab98b60aa0548e0c289abcd139c26b5a31939303821a0d0e4e1fdf5ad109d589eaed2be5f2ca2c870820377553703d6b56454abec2eed6ac65bebd508a334b26e96501c0086aeb4d085755727996f2189b73ce7711e79c73ce39e79cb766102050c6799ee79a9e49a094baa51c1a8bd179e1e3832a052c298b06168d97cd0406f61231c4c8da8811468458e044589b5f1d94ddde8bdd12a44429c5d394b49684b796e30396a2ef6f2f65d2a9172c1b5b6b55543b4a3b28e004b30c72639b81d2c1b9e4805205c083c95a7146a74411821d5b457d42e6864c2ced5817e69f3af85681a2288aa2289a9aca269f246240404a00e1a5db8bb322692d84c8328a4e136a69b9830d733acff35c532d209d1d384841c2f48062769079fdd8aac132697172218dc3c8140db023496292884c69657e6e617676613cd60cc484a2289a9a289ad7d1162b3b754f636633363f2fc8316327076d091157570c61d412fa69cc7eb94c9698f46ec0901d5146c696cee45e80d676ec25594a3af180a95de02c9c90553dba96048d80ea3be6789500437183e96bb96372a8a5bc64e32b4e140889c31194d667c3e6a54a064d792724f7da7cf10993049306a844ac580f4aa70cc208000002a3170000200c0a88e33810e4300a876bed14000954c23e524828104b45a1288c821804422006a1000001100080108661100a252a521d06decd6c3ff9791fa6a9bba3b2a478ffd7726006fa3093a99d30b318691373455ad3625567aa0d2038bbb60a56848bcff57c91574fa9394b08e1a206bbd14d392a8dc5e0cf2a5c454da2bbb609d39e91f61ee11450cac83449490daced7e7b5ff3da60132a1f73f722ca3748aa9a951a7b94e2263045b0ac49eaf03ac802067c5555d88dca2818c1ee36ae1a5539d82154c6a4009fb19e5972eff18238266b93b96dca9cd05dc90ec458016a1639fb89eb11e10b249e2e62cab6d16217144ce41eb9da0a21e7b5f9003eb18214062976726bdeb475969730025e8c40615a897852473452f5167b817d6efed22717a3d0000b744e7e34271c61a5267d655d9878ed86e897ef50c4e2f9143593c8a113c3f821652e2b9305e6cd21e5da9c5e5f8b9a74a80b65582c857549765c1e69f7ecea682fd464cd71b2f17b8d51cf33db4b7941df2401b1b869848ba894aa0a39317afe0040ff3ad331a4a3b69725a88b99876f7eb8076615318bef1fc51df027db6e9aab488394ce89bd6e3b8c48cef1bb3a4acf131e4e40c663bee191fc95d44c94c3041f5e4d35f803f6e620b6d0bfc9ff3ecf7437421c505435f9e06a95ba6459de685d23e2baa41e4f5ba314922d9e43868eb022571abf08002b8949a8be8c76bd630d53a74fa5cc99fe249db95f9dcb70a0b1e4a4ba6bf65595a4ee7f8c08dd085dc13ca16de8e809adfeefc376717fe8382af313d765aed0b3cacb2797e41fad15ca04ec44c4d23b3b5cd2573763876215ddf97a3a2137ce7973c16b331d9ae85f6b4d65329b183ea34c4703c737d6ff55b39fa4eb13fc942f75f565ea0e6e215ac3f7d642c0137fde5b6d4d7648012293634a12984cfdca684ff2adf0e9959605d6496f4eae4fea6f7f0b93ee5cd72a0ad314812287b27362d90dba29f3740be0ea4afeab38579fbd828bf24d939ea455b23a8a83dec8427263233342cca6a51b0e8e1d2be97bc9cf5c52bacd662a62176dfad09df53b3aa923e844e08d9dc05618952514a7ca0eece5fd32aa72b9307a17a5bad71e5aca36dde6cd2c4f350d4328dd0909a5895e7c51b23f86295a825d23b601c49f52a1e1911ba74081524cacdbded277f8c303b6e50c16693dfe65e462a3bfc6bfd74d3758c2899cd52a59d7a2a584b8b02542c9f2309bc3674dd900a430d1412ca4adc521814deb69194459eb29b5c899d14a3c6168f0c11822332ed5fd777fe07ea84cf7ef1e100c27c46d659a041e943853ee918a861188f0b64e464f6a4e5809f2b12250ccc4a4a21ba55f7a3b21142507d2fde961b41e0a95046978dd832acad1bc88c412f8d9cac41b8f420712f8376efcf24851955f22c36809409b4a6bc6ab4a6222c2ff5d3abb178e92ad74c00210c6b4d5ed063c4544e8e5fef147b9ff4745bdf83fdd16d24daed101e86e9714070fbbf1299ebdbc3837c417d86950628bc62a28396fe16766e0a519b4f6b1433b10f604d896a04183c8125428b0b3b92c41463383363490b29e1bad3f1b6dad102752824eb6594e36e4c932541f5c3606d340290a206382774966363d0fbd12e3af1146d1acab4958e5cd490c63217c33d1eb4abf3b127d86e45b14488db1e10423c59e9075a0ae0eab622345528124836d6f040746555ccd0604a55ee6884ac22fc93bd91052c61057329b4626ae8cfef39d30a4e437c41d02f2bb7e94dd51cf144a742952004ce7e49a9b568222859127c579bc7e8a4882cabcfb92542eff91c6296740f0d833945c9763c0184c7eb7e75712144b8ec02e48a9d70b901183295c9229d49f804fc8b4bfc5819b978cd5b6d40e6f93eb9890ac31f896b779f43884a3498f93f9042fef5c7cda922558aa8bc7a80c28c7b282ae793b95645ef2f3cb1b2cdf7a7966c0bf63ac9bc1913446d015a133e1834433345cfd0933212e897c4d31ca42a0c5e44fc0191ed67a165698d51071e40dff3228ce27ee0173d2c67a2751ec3f2cb457a00d0139e54b12435f1f4532d133abca7adc668b6ce3b79ad1dbaddc2a2bc9f0938cdf914a5ca71a4012c34f08c1b28739027c493d697ba776c7813905240160b5fa62acd9c49eaca6bd95596042482d49a11bbaf80fce12258a933095835ee57a91a46c45566c884389999df2eb7332da8291a6cf0a8727194757d9f44e4d64e11f914771009d13c614e1f424e0b874eea58a88b4813d2f7e6fe195dbe3d60889116c8218460101f010a4945f1f1989225da55c3192fec2477dc6300ee972490f85789ce595cc70517738dff0d2e783bcd052ec222699a8c9a34c65066fc3825826599ae8e4c1fc49e533cb966435171eb30202ea4bb0aa0bc7a800a8c7b082ae78bb2bc958f23765280cefa93262fc6adb4ea6e1b76aa55cdce42ce623faecbdd40e033958a6652457aa9ac6d5854671193edd6a82b703c974360c087ba68a050a82478264fada719292cf8ec285cf92b35c41558cdc953056fcecee12c3bc4d468eb52e0e30b09712aa5d6a0f017d128e4c38a4054be442981a688e6748f0bbc3fc072847d66e5390840c02466ad58568369a5cf6e10ff7a81ba8babcfc4a3568daa4c6cdf19325a36e5e38560c6fc38492c1f2bbd4e303234e276da0f1ba7b894ac823e233b4719b0e04e81fb422391bfc8e7d0621aaf2990354cdea48d016ce4c156928125369c843b4b9f018a9997009f4edfc3d18f83f9c0a16ed25ece092d913fe767c982be1ff1b5b0e5f0337cc4b151c4e81a2f10bceb86faec9e57ff811a084b5318cc09ae27384e00340641432abcb72ff895f402e7a9e59dd0bbda4d728277b0f81174e97f2816190c59d5ed341eae031ffaaae377c60f8f2c1fdaf28c2ba53cf06b8303e2091dd1fee26d0af8868ade8c23d876d752cdd7ce8d8b176ec5fe62449b64751426505b97d2faaae293e85f2037f08c4981e02b95ee2a902714c6c187929275432a36525f68bdf054aa38c8ba7f83239778a4bc4c80f69190e08baf89d4a9fd101ffe9c1b3d655b2cd2056901280268b410378cb9a164e923eb320d0403e724afeb0554a3fd868f68ec64498269f4ea81e86af014b20016653a5a57c8a4b500d85f71ec88ac686e6de3e265427e1fb9cd5aca43447bf0773fb132c35213b0d3e94b88aeb9106e422f6e2b3cd0c546f331b456ab668436281d96c91e9e0c4cd0faae9e4d67f90786fe24e14f0700f0f729adc98006ee8b5c86be636f11dd2537674ff4d0a2760e3cac2fb0b92039fc54f33320f8e045f570c8f4b0e964e8a0a21251848ab9c897aa65a1dd57a16ebf295f3279224835ef91ccfdd54f64a0f5b678d39e3890e3612fdfbfb414e73a75d140a9cf858d872c7281cc9af21a10255e6c959ab719eb595131a2f78f8f2fd2eed28a1179096fa1b4d4320099377adfa649ded02498cdc4491186731a5af906069b1987e844200c980cc9364072d5e7b5ccb01132817ba6a5251e40813c948a9b38a6ee5da5f60598d5f0226a890e493c2cacafbe266f81834ccc85007a75750d6425a861d3c1f2dc21e3059de0735663e424e6157e519231e6b28ff10b0853e48bfd1dff9a59622490474b72bb48b91278cd4df565a9a11f79dad4ad9904064d54a735c3560669394fbdcfebe846640ce24eb9eb85e288d909863fe025d4ec042196dd2dd3ef872d06717657e75120af6fcc6c0b964f33a494e3b862f675929a058af1418e6237b28041b1843e19f719239fcfb0d278057adee136aae89f5649d3ca4c71cbabf450e47765d0b9140b5902b330498c3c083d260742fc3c3ff2c8679309092344dc2165a7d4ea1e48673083cbb731eb2158a66e5cf5e52029902b82524d6eb4ee021a16c0c704e3b92c7bcb0820e9a7392b05c85cda1afa0206220729c2dd9ffbbcce660b03cf0e284e8a9a4ebcce514832e212e49e448c2c892e8afc80cc0a08cd84b90d084311e41f7180189ca659ca47d153776ab343c735439cfb6c65cfe17d2404e4672a34a6e076690930b76bee4702eedc06cfa1b1aba10b2a6040b656d80cf229481d31224a4ccbc7ff47790258213e61e41a9cec74f78606fb34a637d009fae71dec156c45620d42ed0f4c2719f9a51da63aadfb8302895de36edd7638d921be6d4082fa3d3d00ab0470a7b9e89535da773843679bf981e40beedc6071159b4bc3322fc5798003d35a8b32aeb2d3c696a4f05a3b136fb553e58f248dbefcdefbab27f13391f5d63e9e2c102f4aa3098037cdc0158529e147a04e312c774fc42d97e7a6596a8e881eb1231875e44f2f8e4064ab9b53a77645a2b08143401a9632233c1bee71fab1585373eda48e316dc2f77d5ffa2ebee67fce7f71bc34a19fc5231f90dfbe42e28859dabdc8c4f2d67e0af8fd5ec743b645996f4c6b73ca187b352bcb1dfcc7a58d9f768d058cda7ac82bc4e978eb0d66ea901a43dfcb1b09432ac9787306ca2872ce3b2db7f38837dfe3b509fe5cab51cf71fecf523518b389ff4e0f62025297653c69b92c2f90596be8285601ff8f3379d847ee179e62a0cef7cab723497482ea97c8c883d6c42554ed530dae30ea907628f4779b2f1f9dce040cde18d6874c737324fcf50bf451846059cf795eaa7175c94749a0a36053adc1d6b4ced44505f021f6878461731e6f9886b03f3117fbfa1bada380bb5750d9d2ac46e549f0a87818ba58849a5c02796bd1ee3e10548d901b1433ddd4481e0b08fdcad21a4a2fdab2749af4c9b2ee6f4487dc53c1237b306037c49a85f3f10616daa6b5cd3b0293d82f886aa7a8e58ae7802337fafa096835118f2492808e4378204abf8919ac8a22438c9bd81ebb68889531e54133f591f6677a434a7e9f27c2a92c98fa0215018ca7d84d06865bd555c18e586c777a04c67fbfb420ee111e70cfee2fb6c6d84c955419e5295b9c7a4a4c77d2b8cfc5f3f9f37b19f2560863eca0d05ff5838454c8e79f40107b60e3ace6159fc3dc3e7f0bae7ccf0177ebd6b08a08647f0421806df210c10ec28953485210c3f8ac0cbd98a379e5ca9d65aad072117b5c100b87629bdd6830aa510862ffa663322b4f60182e13ed6df86cbd10887c62e51a314c220619241e9516587daa7c4ee4960135062dae919bcf2b07017bbbba63e21f3a7eeda3529fd36fed3e6006cf5a9673033cd4372aaaafa0a91c28e9117fe5031c8b8511918e25224f64d37bcc1cb8e6d58476a0195c2a0c20ce55ea1b83168aad4839b7b9eb80a8a124848525a53dc69947214dbd9c3d8181823a1b051288f0a5bc031fc6cf72320da02765383d79690ef1b4741f419de4a63ba638f5c3467aac665caba5018c15db93aa62a4afaf5dd2ad8ea14072aac801d105f41f3c981e7f79505d254d582a7d3cd7b3f83197be696afb1471d842f5bdc32eaaafbe6fc7cc3e660ea6c4cebcf63b80101d0704df09d39eb8332795a1daed33b6c045017a74648bbe17bdc2b4c0a5250957dbbffbac451315d8cbf8c390d0bfb85309212c4d09e16591070a29606298c94cc406b53072390771e8777c54d5e6135e445782f873f9b300552c852197d8ac883ed32180f96f2801e60d79ea82b43b7ab2e1d94ac069757aa13fb357575e9c07dc4308191c9f90f68b4244de236e9791ddb0d0caddade33ff479b10e52aa2ea6d16428b2f011d464c109b2d4298fe36473b128b07c29810018b03dbbb12b97dd9f003fad5c6c4cd6b1f1088b1223a4159ad9367c7bb82944ad304322600a6188baea373f4a3ba60c014634c8a26aeed17803b12c12f8fdf460cdc35fa61274095ead46a1e07546bd800a34ad7a6cf033c4b512acf52116080c0a6c614c07ab8ec43100e59479eaf2ed88800c115bce95f17166d76b4c3c12245654378227154ee01fc1ef59b916fb4600ae60519a466a4a8f44c0f4615831a5001475f8554bbdd14b5b27786da434b274bbaacf62ea605350059e25dfc480bc51d4492d615f8f49d7581ff1fcfab9d5883baf70b1aa6eb8cced293688df06359982fddb76ab37c04b6064fade9d25bf010c9566110aa563b7aa4ad714fd3d86e9bc22e05c1099d563f3ad2b5d16e7108d067b961b936082b28e83f6b00b3ed978af2ec70b8f2c2f0ccf9db1576398c120518f4182ce2eec0f6d2e6017f10883c2da30952d1bc2f2a39b77bd0fa2c4d5aeec475b6456f9ef9e67e1c2d0297e7d5f66678091857ddd3b22d3698f6fad114fd414a80626b4463cdb54f305f0fda58a2f60d199ffb29404acf68efece3e52d2426a5ae19399e1072982b91c2164853dd3b478061717ae708bdf050f84cba4d0c808f48d547cae6f244a02a8c2a6d0520f151cd807d51b39d6d75e32d660850d466c6100f13f4164d7f80883ea003a04b42dad1f7248b7e0114f3fcea36031804a511112a62e52b04b89bc03083e343268fd4d05b61db6f06c4ae488f66ce60a3ac3f19bc6dba15f22bb4879bd1102b44043b8c70b4afe49e4741495f79299a95a2d1d44afd433c990e523f2dfb85de92125d9f451054f886149a06c8a6d529d87908151c1fdf9d9193c12f573a63cdb1124ab3b0c3cf1e8a502ebc31833e64c7623fa47ede3cfacf8480f01480c3088360a5b6a84158862478fdc35508010259eb978a63c0df0fa5e4e023ec86e5865bb62b5fbdc7abdd09ae43d77f5a098bc274a9823ef774ad27b9038dbe0240287cf481fae265471f761f834d8d9a3bb5ef1b4c2c10bb2fc19f5cbc1751901da151512758a738a8b72032963cf202d49240a72e5ccdbc059bba7a3791ce5360e6ec477b7435a823e6881522cba58b4d70cd7dc9b11762220c4c2249ae23a6a49721d5be6b96166d66420942b1337664446ebc5ab3509e66b17d137da72daf29978c9a290707ed79d61a9e76160cf4b406c17924d0eb6bbf6c8662a045ddfd5ab06c90e9a520e24b06aba565d986c02aebf67e3c77c5a80d845f386d9267e4b9497671f529237434e3a85f45f9ace27edb51e9b8edb51de0a3ea401d6558186a2f6ee73c3c1dc33f1691f2c648a6b7a3b600aa31540a72adea9f05fb9f8bd506effd5538c57202fc4bb276fdd263f272a1cd9dedfe0870a0dfc836511aee2d74839a71d752b428a83faa22c1d5b858dc9499ee084008f12aef4d9d30f89eaf8b78a5976d58b3fb84347133ea3da9eb2ac0ab46cc4f475ff9c52e0cc556be15a70d0689023d50b0716f21e85821b2168a8fd064ef079dc82eeed535714ec8929620ce92619a5c8681ff0c14cda98691585475da29a15a38ce1ea60d663a028d0c45c0cf0758e00d0ac1190ee278dc5c0083b901917569befa5c07132d7bb1d556e82a47d43b9fc46b30d8602f3b6c4505d1e1e1a412a5cb481061fdd38cd26582c386e1df9754808c03546b41e0640503d1533f75ad1e74b943a5f3f351f9cf8f46e9c343036a6a0a33d2e589fc035f0316853eb8a1dbb1173ae4b6ddfd3d82574e7f7dc3eeed2e4f03ac60de0b14ffec6eebc9197a79411002b91de88dcbc7a426aa6748cb4f2e14830229ba5bd4a505e0b7d2b0ad1fecb2d7d7057cd49ef167baca5d9e1baaeb419cc6891dc96f63d17ca4d7bbbf577f523116ca4e631cb6ee099fed5779996c6f67bb05574c3b1184010e4232e22ca99dd16c9d02fff15975078c3831b602b3de0dca623cf94e6284bbb79210e4d4a772ac2cfd98cfbe34e483efac0e061064d3878800b71ede0f5c2b98850d3e210ac1e7781fa92370c684019f8ce48cd59fdc980536d96d8c6e0e1b71a3eed8132c90cdb931b604c99193cf659a3f41901690478893cea18b2c2aa4818e91449030f3bed48ccf3f121d16a3d5039b1d20de89abcf3adf7e7e3080e83e4670e3edfbe9410d1dc1298358cb6ebb79cfbba3266284aa80cc283bd63bb3f276f5562b03f13f013922da47f4bb8e77a8aa9edead140930b32d8302d876cf0901916cf87dc5f903cd549253d2b35c4e1fdc4a4321e78ff214317b2cea3b1ee149aa23250487811e63d043a2127cb4b1a40055056282ef2a45481d0500df65b6c327f560fbf82ab0a339096380ae393f6d7addd1d2274214be33148084d340f892fd84af3d75799a54ebcc506a1944a42bc7f93453cf12b31ea912b8c754b0847c4b90b5869859805436853498d5a1f635a74e7234be9874848d305861804c84be1b7518fcc0683733611ef08d24ef465005ed38675fa1b1fb672fe96a7e52ba9e4366c4f20168017655afe9693f85c6a08e1c1553e21dfbab9864e449511c56292a6657e7ea388bcd615e97103dfe02ce0e33121634e1c4e6b496180df48a4e2a70bf96a272323ae58e771d6b97546dbcd6aa7e5cced415cb661d7d9469a186aa16dd5a5fb310020cdb079e6afb8e6dd4ce05a13e7c3282b8b9986fa10a8cc74ae7375c94f553a9680172de8308a4c13dd1e39c901de59a502985d10825471aa06182ec743a0decc03fc06ccab448d7f6f52d603fbfc0f15853d1ab42dcf8a227d51d986acf6f7cc10324cac1c3314b9088794b80b73fd91eb2828e46e195844e4c89d1107b895adc06f4ef9f620758224fb6ba30dba71660e8cb68347fd2a614ba1129b0e8b5d6314f371ae82c827b23dd697bbf7d8eda2296b050b453578625928f187ad34bad1afd68ab38e5ccdcc4f3d3708b458dbfa1f1b4e945920bb60833666c08beda7d1ce98ced4cd47dc91e0e0d9a04fcf2a51968078b5352292f16c081cc82b7bd90efc63899ee3afe35c534f9eaf78b1f0b3e1a36153ec493470fb53575da7b559059dd3a5e4dae1ec0b5c8de7922981e2c6cfe2626c664da2307013804c37c153f109c199b147df6b78382c014673e1fdf178fa4e7554294b4530223bdb70b413534c81c5470b6389be1efdaa1f8ef0bc210f84941f9026a71b5b42e026804b21ab81f39351a91c9d10ed4fba95eaa82b27b91c7e458c2979fc26e5b92bcda7a9c9611a0d2c4b69b45eeb366b8ebe27de233b4d90de0de29e83e803b6b9c6bbcd0172a8b00d7e9e07e063c4602ba9c995b90ebc09c86c7ab9d3bff16adf12ac2431928730675c6d0bd16bbc4ed4e51485fb101a5861519e591d2b322b64b2f2749744e94c27062f1f647946212bc86af6b95910e9fc89dc696207014eb496f01c287388825c8d2ffa189258a1990551f1948f77a6024e685c06ec1ed37aa75a3259a8a4880e0579f2aae4c044325838bb598516c280e50682c4c43bfa9633f5a007e1e746bb610547bdc5d69d23ce0c71510ee3a5c6c7e0ac684977cbd83b8955287db7da12f1499df3143d8548579e0f6c7cf44e4f6e4c4cb5f7f56f02299d29016f404cfc18e9423bf9bf641831556c7882bc6bd04cc57d7480ca2be045a70222094b674eae83983cf76415708f299e3ac0ac786e6401ae34d4fb13689ed68dced9059aca56e5c361ce9ffc2dc2880fa02282832a74e8b319162465e985751b0c2513d77cf8b97a0b40f8310da92581982a37af52b626951c29376f85b6b2414f2b152500b9c1704a5610833ab5fe85bda6815bfacf247b0a0b1d926b015addba8e2aece83e05d4e057409e7be38a736269d9f14c133e5eee2dee92116b6609ad4a36b753443cc403bdba6549fdb8e8bf045cbd3838f2c00153f161b3712e6f7c6a81df88a4b6462365680daff0242022c3b5d11ff3285040f66e5372f17725041f6fce1b29ebccdc60c3dd986fd32ae34d6f85e3c9aaf4cf83f866a5252f740537f317e7d5ea4ed5daf5c00b0cf1ad555a728466fe6280db3e5e53548beeb15fa395ebb4d4d075d22d488d91d0e1077743d27122922595763edd1c1bcf8c52706835b91a3a63d10ce21cd2e3e8b217fdea281590c5ed80fd0c1b0045e5e98b074cdbdcea276bacf82eb25e4f34230c56dd2ad7e7ef91940406c772b188a9efd549f5d2d16e8891f81033245931940c25617fdeed82c65a09b61fd327b84415b805a9946e68a83539c9c52840e503ca2c20fe4f6c8b46200118917cd15ac604b6c21771de0a2530cdf62213d124b5e397373f32b6611dd6a6967662d460ee20e50b2f221df4e04d53240510c53b5e296d8e4012bfdca7c3d68b24f374157c52c154eaad0ea408bd68aad00ac3944d1d3e9ad631a0c1dc78d12aad6b4fe73b561a7af226b120a3ea1faecb38bc6820922d57c8f6b35eef4fe342b702a9c20c19dc9c57ce4b8706c05d35b775c0bb6c0a9a0269802b44616b562eb037dac5a76813d7a79237979f344ba3a65bd81cad07c9b4c74195597689be6fb85984279da42e04d77aea927153010b4721fc1944e25486178020810a556dcf554c84beaacde7c4b218cac5d9f9380ed2aa39380090852eeb38bda85dc3d3029b96ed61270d66a6b08d80341efba7b366ad1f5c3aab7daaa076fc119fbe9064c0ae73e8601811ab7de82468ad78d11fae33b0daa935a0d19b3412a62760851c7bcbd5ce6b08d082e1a00b222389e81765d0c2ddd4c961e534cab9f198a69055503c4339623d83acbef86c123b50d873676595ae4f03e3cb5d57833eadb30e4586a999701475cb858d38c91fb6818d2ffa1927b82f2376a82ff25ef987f3d8a96413d650cf2606115aa7a874af433be0cecc51bea99cbef81b462d24db028a7ade516203c9a568022d0e28706ea24a9771b27674ba2efd566881648722e214b28e79423e4611a9f02708826331a2aa6124985585a8af28756e9d8e723d32920ed69ca0ef511fe32f3d75a43cb223ba72475d097d9877e6f3691f31f0b8c8fbdb8863005fa708052080a6515fa814737cfd18415f2d9939caaf00893d193fe47b22e21669865e7dce092180d9028e0019e5e024ad7ac77941fec01662bd48b3d5eb4093917464dd9f5847f7c53b2a31d83b2214f416b7ae459016a0cc35129a0fb7a7a863d47145156d04c4bfe23845052f7af54f3b1578221849bc62dd420fbe99e47b9ebf97dcd129affc7ffa9156cf08ae8a57113959acd1e20e874a915b26e1bd900356d0515f5cdf0f133cbdfd194561864ceb7125aa12d8dadb0585a010c4635764198afc98da252a185b486fdfde5cb05bddf470d0b3aae0f80de4d8104c0b0888c3ad2374033a852f6fa7279990f0ab4b54a3970a678628ddf98ce02d91160adcc4278da1bf8afb656b6843e4a70dd850d909c1f5a0ebba989ea32c5c658cc5e627a31b550d4470a8cc3659af3b108983e16744c2a96ef8aec87aa7f852da6a5958558113cb81ea80aeb5de0a8c6d24ffd341e592c39cc3b7a117ac7326dab52b2e44aa64eca64d9786a6489890f717c62f750ac4ab2c3baa1d5eb7e4c2015a66baa59c77eb22ce8eb0026d4666d8dfdc474cda70b022b2cc8a366ceddfebe4cbafc4329fb893d2f073ed53641536c6f9d84f2f5d646d87212b18cd77fa211311ba78a112ef8e1a12708f39a2c2b191f83ed6683501c9fda597d9825bac133888e5783fdf7462c23e57379d7cba597b914d351f065ebd41d8e4019504094606efa2cf36996f4301a5817d79413f9dced5153f4092b651775440a84bb8b5dc88a2a92e28c38506ec493f7d9ef6237d03680c29f434cf6e64e5619102216300708aba5ed49de04320028fc4e8720136313667aaa539144b5b016a68535d2dd145090ece8703da4efba40f7ef839ed36f74193da6032d847d1cb79bfa5a4c1612c5207589edd03178669e6257ce0800df96048fd192cbf4113d4c03ebe21a58bb9f333ffbd858bb17001861389b778f70df16c0000cc171bb66137109fbb161c1b36ca346740467fe193eb35771432a502d56092569b3cebac99a1b7bda2c0685c6efdbe76cf5f44d6cc45ee4084ba9b26fdd888c5741a4e4da413d0ca0e4d47ad5a2304a3de56a2a0893e27a86df463c2b838c944884fc2cc6d44cacd19d110ff91866e3dad932cfeb714016c76119c86ef92f17e75b3c0c07e5193ecaa18eb765b66dd4105dd2a65885b6b4397685bd7250aac6e2ba40c339d1afaa17a613412246158cf4b0c79a4037b1364c07eb958bfbd887f932ceb0fc80e045c0a00ccae1accf81b91e0f0479ed7213970924ba692e60218b4364073235803e72164eaf0352fda09b2d03dbbc10bf5256287dce189511378f5e183bd4b7dd733d2a9f38eb2d0408d98709b896060f9f5348f5c497b447769c4f129ca95a2b1c5e6556ddec87e7247b44c61f5dfc4c48d1269eb402885766afe0da0f1ebfdb54bd5c1e0532b31403ebc8c24ffe2ae385d075cb0b574e392be7dccb9ba8ca0062fe371148f6f226430af9d79a92acf7a7c1d3d6daa4ef097b5210e0ac4ae11afe44d6edf204e3e7e0cba31b9a2df3ca501a4be3d004c9e67f9680094da1f7db79b9286c257ddeaa604c241b4e8712cc8ca28939b5f167fdda79b9df02d811c54592ba87382f1d3a8a45cc81cf6c4ec41cf3426cf916e88864ed2bf11e70216f6082bd4007ef713ac0ffe31b010aa4e94a5de06e69b3889bb214b14582cfac6744af291a12de9acaac1e07a2aaabc79cd0593137c7fa393d61e476e24099d2e561c1fe8ef19847d7a2cc49aa1098dbd164e39e44188221acbb9defbcb83fbe074d7008a23f215aa7a1e052ca81e3347356d4347c887e3adf37986be6669d29e08f9e58a81fb6fa45c037f1ad428bd549348738a20752061a2fe7ba0af0b5e54121063fb7e5298910e693f8a1a5c7449ab7bf6994620787438109a6db6b657264faf1bfa4d03405811b367e18c7ad50c5fcbc1d8ae79ae6e00cd4f47a4969ed92b96267bb6b7b2d158a9066217befb4c622c0a4a2ffddc2e40a8adcd085a59b1518f30c011b7986ea18629dc78f2efa92465a83f40b2c5d8aa8e299e455c55d81af78779d82ab0fbd5dd7b6ea2d648b2d6431c454513a26db370b291ecb0a52f208769411ab70e67ce1c245d1e066a0461db3f0957fdfc31bd083e5a58c190921683ba80ac0f0e634fee4a06e0554ac495af5be633fe9278d9ff188d2869bf1e18dd33b29a5e41011b08ae21b3710ffbb1fc24c604837ca89d557f0884b524bfa093bb14f2ce95b2ac0f977962d8c550ffd3e1b943ca477ab9622bf2f42d55d855439cb1d911c3dea6184d064ae174e0d44451e462b36516dd753eef62bc4ed1038cbd55a753a6a214f6b69efdab8a1bbbdcd726ec0f43a6ef98e189520ffac0bd22335a0fc73a08aebbced8b065f2a21922602f7d93bd909800dcb49510b911e1ba9ec4683acbf107f0211247d4fc703fe3dec434cc017c14d81fd083a9e27bcc8b28f6bda40502674d0153d8e42661926e80a791c0fd921ee02b0d1cb5baca27daef83003f879321d07a88629fb63261b9d327fd80e0797dd5f829a8899a7acdc9a2fb632ee50d68e61097fb967f10f79ae3f48005303c4b053593ffd74ddea5370475997660df1d99b9713b48e96e49858c8b0c8ba5dbd8f3d3f5ff545c26c5fbc4d9416467aa72a4f970b77434a54fba472ea722dc6647aa6b59f169812e8041ddd9cc44462813268541fb91114fd3a47de6a65a488528fb262c0a8a0232d012103ec7bb96f70e8f69f16f67f8a81a11606769df53d3552b73cb7cc81c7ea5586ecff45c91603d94b0029d44152630952eaf2ecfc77310221d9925f987061018cbc4148154b90c58f936fe66f0a9f44c77c50b20ee66262d3aea8fec277ab51c3fc967153f89aa425202a43887f24d54d9fe9455fb6a9e98fecc4438b782f75d795a41ea998243d842c1d3c4d902e5abf5f64bfa4d335b7232be9a20411a5bceed109b549eb46fe91b0358013be0d7e8ad0cde564da3b2bc3c9794de68d41c2dfcca8c340b7087f8b45f14de888b9cd0da3cd0b59907b312256576c0688fd4d9da9863b1c389c89c7a7674f5c8ec2c06a81be44bf1a898c2785b57860f12661d23ccf20549cefca2aeba1faf7dd09217d1945157ea7bd36313130008941002fb2934c63fc68858209499e2a31e936509de9fdd5d5e374b2700a7b0e5d680b7dc25c1d981002098e783a3501cdddf2979fd7dceee3a962083c2f585197efe876898c4e23728cf545bc0e6d1678a65bb78e2018b0db7cb8cddba3fe52ea860b6c88c745fa698435e80aa8a8e6e0c7bbbe7a99ab0ab6d0e4f19040ce27094df4ad14a996f64271dd2b5dc57e6a171acacee74e52be5b6f1cf7fb1dc5b86894eac67eb82b4beb199b29691bf91c496a244b29be69520fa8c1966ff28610f543014ca244b57e6402663be079942bf83b767e4fb44afc00db2a9dbe62422e1320560d8bedf32a9c6aa6a1131879db25be37e40c22d1f0f0e75549e1bd21409fc8a612ff6a4fd7e4997b55c1f56e8779a874385d8b004be8288665a7dee09124c9a173340528ee73e29fb1b43f51f143db5de5e86869fba89e29c27129dbe757c1aef4a0b4aea2499664348dd455cc6f96876d0bb56e1f520ac4517c689db730059003167b47444485f4aa9c516eddbea31d6e0daa1c109086d92e160067f9089b485fcd882e8668627a8f0f376303b8a9f68630a003df4742bb353c98d880371b3d46120822e7e0ba576cd40d041d1ab572ea601e08e392d135da3a82355eafeea1f3ff31d3c1dafea82a93a315cc823e782b4cf6e8425dc3a99d77e8f02951d80f9be8d689a296303f5a56febc21feec93518e34d0dc353d6c7df01a6e00e19d026b896a96ee1e320f9260705fd4ceb1270743ef38cd9ab639644483c62acce4e0eee07b1d45d2d31425d43a4ed34b93ac52b689062d01b403eebdd3a090a070bc77e0d6217a1deaa7d3148a7a87be1c683aac3a585d07abc7d39458c892a1e1c8462d0b11a68e42ba69fa25235eb4b2652838ca8ed388132bdbc1a23a9aa924ea68d5412f87d81dee737077e8ef4083c38a83de35a4af67428edfa91d13df749f5745430ef438ace300ba0e92b1b42816a991d3641e19d9a0b51568ba5173cef6cb2371b4582023e568b9a28923d0a1e628efaabce66c7b8e0966c40186314b395683a6d9c9489396104446403482a40c8e4adf71fd1e1bb5cabe6448ca6a7071a48c864b72a363249db640ca0a6683de9d4d29fba0a570322248232859d661b68e7652103aaac53547c3a9423cbba37f743465b93e877e07a2d5aea466b965998866a1c30923e448b202475743eaf233708c91458c6b9510751d7a72601a8721d734cd2d245e6b96b6b3aa285563d25464291b4c239620cb41a48e96a685221368cab2be43bf03bdc36a0d5790c638672c346878700dba77ee5a2e244c1a54924174c8efa2490819014e2396321d741c30e6104358ac9bfe4e9ef14252c11922641d48af438d62e33f5634f4dd027a071a6a5e400ec29a0e8af1514c28cec8ce63f38feadafc106de6cb5b99a17d8dc8265b61ffc8691ede2c47ff8eb4756e66846579686f7b197373ff89a6139da89746805cddd41d204c707c55605d43ec356e58da6432d242dade7b6fb9a5942949196a063f06d1062d248ffbd427bdd32fe522e1a5dcf54b837932c2e8598b615a74aa620bbab2a8330d4b273b6708963184725011a3551192b1a04a0b396856e191e7b74c0559936c69cad314a7294d53987a8a6c8a914f299a02a35388a60cd529415370d8249cb4bea1cb0942454c9647b21c79beeca09b73869e23b11b154c919a70124d50601ff0122c252907c6ae47657994c40e7914b5d88d070894c8110394254a2ee0c2d3c3112f40aaa10a275f40d09eb8a01dfd782c9c182d32ebe5a1594db3374b2c3c51e221868820e418211c7010111292c4488e4e33fac1f183e3fe782c1c293e64f608be4f3730a1657924450794a72860a2308181e9c245142633c68f162589a669f4c763e144b1f2b2a903ee88e011d3cd488b0cb73d2ae2894c878517407894b117300c05bff88147fc62086fe1bd3d40b8f75e4d6707c6ae9f591e4101936fb23c8ad203ca531e45fa52ce49fbdea6d23f1b94b664b43aa5c2c8a3547c4617547d46eea35a7daa1955cde8ff8faad936dbc2b448e7db46d2921a29cfbf73faa55be56c5739db69defd34ef7e1b8a4b6d282ed5614fd5614ff5ad5032724e3aa59cab2933e7b3e8fc194ae7cf396773ced89c47e6d0143293104da129e79473ca69829c724e39a79cf435bb9dd6ea3dab6ca7b556ab7956bb57bbdbc6755de7791f2a95f29e29540a7ba9af0656ad54ab95f75cd17c2f1957cdf79ab1b969dddc78cf1bd55763ae3c09ca7caf99658b352438d39a69b5bc67ab86feb4763a6a3c01fca4f44e7f46f3339a1f8d4f8a916ab602ea9eb1aa6661542a6a543f1a94babbce0fec4e93c50ef3d900330d37a79647456e95d367a49af94e8b6854cdf24cf274ec49fb34920ed7f1f414260c18305fbecc9f51b01f40c396b9506b4680295934264b6fed41dde59d281f72ffcd5c9016a28a244f4a28282594129a2d3d5fb07ee99dae4c95a97ee92179fab9a0a1ee196b8fdc41dd3339214e48f78cb3471899599092aee2a8b2a48d8a8a8333ea268bcc19b90fcac977fa3b8843cd646ad8a8c8ad248f2827d44c8648f2f4a3661e24796a2b735238a31639a3165b0a6a6694fb5340dd330b8372423dcdf72b33a3a17b491bfef55ffef56fe439bbddf35ef4ab0dda547a4f8a1a6fd4af2ef76dabdfd5aeb5d63ae545f5bd365ef5032d727defce72366abc91ebf7baf47b5150f2d4ef656f22a0bdfd1d9bfd5ef546d6be1795f26e1cc775dbc6f9085af8f3e33b1b573b97ab2341b3d5dbb65a3f8539c66df4d7ae524a6bd31adeed7b49d9715f37d869d3c3d8fb46d0de7a1f095a685f32d7ffbe1064ae4e29a52fda7d36ea7f35bace03af2ed32ed7308539668ec9f495bbfbd5d872f7c9fbd550854cc3c63eba2e25bb6eac61f398174e580bbbfb734ce67ea449524a29e55694bb7fce39e7eca6dcfddddd7d879adc9d6a4694d2ea54ab1dad92dc2fc6e0fb9ba77d2268d9fbee6a7fb3a63de8fdcd5efdfe5ede77ef9f0dafdf59685c50ee9a943b1c654e0a812ca350613eb79b1b14214a7f7ec0b4eb7460a84f654c20b8983f47c42de186382515491d2277fab925b2a7c620c52ad422924a4335413d528dd42295481daa43b821f7e16075862a43155263a830d42052864aca58714335493d411fa0a5c2a919968cddaec675a8cf6b7130154ecdb064ec7635ae437dde4a8553332c19bb52e114457d5ec76d576bd1b46cde88d88498621c8c83b9cfc8156db014914b29a379f9234e09a58ca48252432d36f782dce9a701391e7287cb21450ea8c517381738205206fe7eee07e7836b41ca90f97eae0717c4f14805b94f05f21d16b81d9c0e2e0757020e07072465544f050929c0c8cd90fb39a13e40ebfb3b4cf3299a57ad687ee60338db97013fe0ca36cc8126ac59e48e152976162976ab7ea94fe4fecad404e8af4061059a016b5255923cfd3260d52279ea96ba54b9a4c0ba040aac5de8075624c9d3ef81f58ae4e9efc08a45f2f45bb066913c5556ab489efe0dac4a489efe0b562b92a75a0da5bdcd34606d81b58bdc692dac5d244eaa32f799b202c928c8225799fb705ce52a57394da3dd5a8852011057274ab4825d499eacee84e8fabb94f46f97abafd6dbe6dffe4dc32b7952393ef38d3c3f1aad1a6b7888432c79be17abfbdb060d41b0bfd7eabef63bbe9b08dcd79e84ab71e1ddc2ebdf4bf59a366638c3c63f3535c225e1863fe18fef58d46bbe7cedb33143aa7d2f4f66edab31d3dd31626c5ff8a20698fb5ee0362fa7d94a5d3687a21f5a15e6984c027aa3b6e644f752e23ccef9a2d95fca2b25751a065e3b77db795a57a9267586f0c8ca7604d9869e27b93ae747c3378f4a19946ede95f128121a58eedff2cd14fc80881653908b8b484d914a82ba826281f342619c2ac80d57305c44ee97a92972bf4c25c9fd127525f74b140bb95f7e2ec8fd52cb148411c3e30ad612923266d3e611e9a070623826ad20b97f32794d5bb882794472ffeca0e4fec989c9fd936392fbe7c625f74f9b29e8628899980bf60ea1cdb41aee0c11b9bf5de86e2ee45ec1ee10b9bf35ad86dcdf5689dcdf355310c7896b667368b509758206b9df3bf6a3cd91fb5d0b57b04a9dc8fd4e67c8fd9e29c812028f2b188ba81d096f27cbaa21f7d3990d1d89dc4fbb75c8fdd452d00b028f2b984cca985a70e47e192029a3ae603255544c9348c5f4e3d492fb6bf5b0528d7e09057095f5ab62ba52c67cba5231e519ae6051f0fc71b56d28a78165d9c18d6db66e702b9066999a9a2a7d400bf08899c0249184f444062b829dc02fe00e83673a6cb23cb26282bc657964454811429dec523de0d95ead763f0668df02fc4a9ed94e9a86dac8c94926d158ce0f393fe4fc40a783f427b8e59c1eb0d768a1501d68e514480094b1bd914968915e5a34410d4c1393f52313ac6d3441fae434bb3f41b73f95f4d29cf64bb01498a367fffb68d849420923aa0163eabbd94eabd552617b65eac0d185b85132cd591e25d33889b49ad303a6df1334a145fa394db064aaf36915c247747200b806e59b692cd3ef727ac0236d80cde38d4c5ba4d49b35c8d3f0c456324dfa26d4099b3090020181de329f4e0d76ee72c4d0bf41b56d732959def934868f6851930fb59153be39e94a6d468b68d12cfb5fa2392d535393bf26ebae2ee443fe1a903b8db5511b39657f0ba3d4674e4e4ab27fad2d9326d154d2d6c1647f2b7dca241b7730ddd3efb1ecaf4919eb9ebef3ef0cc58329fb7757ca903de02dcb0ecab883be07d80ab25c2d7a0e0bf0289bea674093c9b77dbaf7cfe901fb90fd3b973cfe394db0e7db411d986ec32b79bad2c984a5cb119323a6ade8b0023467607a29c98d9ed026b04a74c42af1438920aa5571aa12e656994dd1e5c0ea3c40fd7e17ae1ef65c2e705cc628820433a992822a4bdce6e2097f5085471522982c07153132a7195c9e68810a723401d2c4087468c18f254b6449a20a9725203b400942a905463ea42081022994f81266c9519125ed88bd957aff782c9c2a4f6022628886602b984a182a60a82c51416202e3249ca81ca1e203152646e47cd12328201df583beede4d44ede4e63c7183f70721f24a0a5a02f4260be30df53a78309a781dcd3679443a406e5b1ce5c3a45b568b206724f9c131378ac33eea9ceba87f650f9bd280a484a7713827ffd97bbbf58acf6a477d310a7e64fe956f268ee5ac5f8decd7de75aaf366adddc31aeff92d9bfbe3fadafcf467f8df992ee35ee57c31b1bdb0adc924ebab09ad6755aa775e02866cd7eed728ee056eef79c22b80b47edbbd9badfbabf61a875dd77dc7b524a29a553d6b49f73ce39db286bdfdded75286befee4e9bb2f694d25a9435ed6bb59ad6853935e4d480fabef740fe507f5fa947bd7f36526152d61ea5759f365a25591b3d296be10ab28c82bbbb5027d43e5764028ff8b6485d1ab0b197fdd43efec385f8912459ca981cb2d6badb25b2c7ad92ee7177777777b743ee63dddddddd5a77afd6d65a2bfd4ad3aaf7e975d81fa21552997288e0517421cb4181bd3dcf531170b93ccf59acfba294865e2ae786ef4559ac96b3bf176e516e360c691016cc1982e7091ed422fdd162ce103c7a50a635a945fb63cb6673ced9006d6ca25953acbb9b7e37c5dcbd299629a5b5d68f868f3289863b9863027c7f7e776bd4bfb96afe1ec895860725c0f3bf570d9ebf5a3876586326d7a056ae61cb5aa4e1fc16da883afb5d2a27091e69b6f946b6d659d2a53b97ebf7666fae3fe7e76f43a8d54a2a5fda0897046b3fb7b6dafa79083257b7d37eaffaed59cd82734cfd2ef580fa9a779b1b8a3e75800d3be986fe09e18dd94e6b37ed0eef3bd47f4f02eaf33a4dd334eda67ebd6fff8adad719f4191975777b0f0db9bbd34929ad45b3a8563b8382ac36caa4a41c2398be954fadbdbf7920dbdfb8700b67f2fd1a33337926dfb09d344ddbda4b3293754f2bcb7f6a89bb91da47ba0349190ee3ce649bf9f63d1c69501ab39d56abdd8debbc0f95c2aa950c6ba645e3aab11969d4f0c6c19b1ae2489eb982455e72ffd7d4e5869f91e4e97f61033c72432a2a94727f46b01679862aa71737c0e34a3672aba7dea9afe3ba90dccf24d71f3f21f5476ea89d5a74587ed9e0d9ede079a81f71fed195bbcfdd07b9ebc4163803b2401950fa8ed7a1de9b1985fa198d3ff5ad54dc86c35438273751e0cc9cfd723fc8f7de0ba37d5c62ff65727ddd5cde1e0226b88f2df2225b344a265be4b2455b68997474c0e30cb241f68b55f5668319cce479f3edf4bc8e033d16f0ccddbcf393be5367fe7e6cc3cdc4723db21199f0067ef209dbddc20ef6420c1eb98ea87b9c6a3b714e307aa7d6d8f2168a1dac4bc2d4b59644b441cc27d8d76add8098bbe9554dc3c9dac8ca9accbb8fcc4a15e2907b2fe4c42570ea51ffbd0f76f1382eaffefc68dcf07652a43a7cb194af38198ee3388ee3f0a7be4bedc033639427138e627ed1a842d4d7189cb9e79c42fc473fb5934c7d318f798c0bf10dc71879c4b7c268f2f61ef37b311ebd7bf458eedec2d4500e0d78b4b3d9ccb586567f865f3b4b1dc9fd9a6cb6823339ec863b5185ddb091ad60d3013187c82385848531424ca92269888603164c86846cf882021b805c6084213f4ef0821fc43449216310020c2ce040c41339a4503212c40e43d420c31020bac8190466a00208103f9ad8005300162650207d71818e235aa36200537080a4ca16237ee800827c79819b2b5da0cc40830f2223f9640224848c14e99001952e4d607c450926454909c6ae6f42e9871c2f78bc59e678c1f23b1ce7bbbe37daf3c68697d056b674995b64ed2d27b42a113184f884134dcc765aad7637aef3542b99232c376081a145e3aaa137e8726c80e77b18982e282a620640e00c4e48b628d9d24948be6568550495e1a16e09b25c159a27c5dda2d8b82e9ccd5aa2785a62ac2c8fb43041c96479a48588162058698c4a6906577ec8784858f3bb99241c2dd414341e142e8e4bcd5116249b569647599cdc1c6519b2627ec7c2c3e23493e51116265a1c61b9f2f33b108b1033591e2969e940690a38bf7be149f0e88a979ca32b4857a48828ed852e670678be97a39b43c47a00e1436de43495c8a40c6661221a892daec42f78bef738701079c4b19940787479f70896591e5d39c2816e0ae1c40819cc44e8e677bd35d899abdea5b6dcb514a1cceb966ad56b905b860fa52d5766457030060409ebc1ca0d6690832f47208c18480c358dc4c3d5a563c07243112c3004510a62338526c50d14f4000490189ec44008cc0d2150406181131eb818c1c11625417492b6c4909384c4e3c58c10b41755f02acba3a422927888a1832d412e8455b4004af1e3a3e8d1a24b0b2c94585811c5059c988f1b26426cb41089a1c6b500f9e1521ae3028dd20c845a2f9a242d41f283951444260987d06a0a175450fcc05c5a4821f5403999e1d341062fcb1018ba2c412f704cc8b061b982e58817b008d143498b0c4a53ae5041e48a97a12b4840ae48094a7aca34591e6df141e9471e638850a6ce8e51ccfdf983dc2f6fbf8b892826e08124e43eb675846817ea47a3439a023c7fa45a77adb556dadb4743c70896e148f3c4eec5881c438cc98982a1e0f9120848f22081e74bfa34749f73874e76a96e903a50ce11e168011b74d2c5d892eddddddddddddd4dab6dadbbbbefd6ddddcd755e7fdddddddddddddddddd3de79c73ce39a79c53ce29e79c724e39a79c72cef99e943d3252f41fab8e2cbfe2903db82b504d76d92f655390eb287a826577f77e927d6c0b1de9a12662d24a1ad6b1c909b5e82ffe80bda4d6622dfa670bead8a129955e922a48cae8f757254919f39daa208f5e527615136d09cd9c227037933cfe35a865644ff531d6649f2de43edabd18bb5cff2aa627d9bfa35f0d55926a49a514e4197946ee1965f7aa0fef490dea1e8f08d9d18013e284704fb820b8261c132e09774493a2c5b427bdd32348f654654a2909729f1493d45211124c8a922744410c654f3511caee7dc9ee4f412f69a977fcb50e554192c7352f898b491e7f0be40ebb59d56256d3eedd36aea35de73dbbbb715cd779ded779df8742a5525845552aefa942a53056a9562b99958c0c8bd59a69b568686a6a6c6e6e707056ac60d182b668e13d5bb824385e098ede956087b3820565c1c27bb2689c8bbab171675a2d1a1a97aba6c6c6e6e606c7cb1183b3e2a25a16c8144661b4c58480a6174658053580b1220324a2ac42dffc806315f39cfdfed51067ae33c41da819058765597b07313e4c718115295680b0b2c4458482ef959a36aded5a9d52ea5e3b67110a9e79bcf265f768777fd119a5b5bbd66aed7c2de7e868363b4a618e09ed0e2323a376ea95525b6bb5d6da27f7a391f364368bc568e43cc15f03b6fb92d5269d4cc66a222299554c3585324ea1b2fc9c17b4ac7bbe2c7f5224236a9ded23a90faba96d56b6f9b58f06adb5ce9738228cac7990b7cdbac7c2b6a7cb6b513bb985ae4b492d07dc5d4a7a937a78e527428c068273a4ee77ee79564a299d7af7cadeb6b95554ade1814763b6383f1a29cc2ac48cc92ad09fd94598c99486f6db57c383dc82e39ca3a8fadc57c3834c5ddeeeb17989993e4ebee0ed1d1bba954e5135bcc975876eced9002f42c2307d11670e9ca024a28c19c1d689a4f91fb40bf7b370b2f49e08cae3e3b8c83f598927c10b920464cccb131d2b18e6171c4de429cba326c0e42c8f9a88a2092f4c3835b1d4c415da84cc864e07ca9197264c3e6cf2c5613250972f79be045e0c9151591e75b17243888526805401f264086c00359801092735784296440b54481e3f3f28c0c1693882c869f02364e81e881f1c38948a6490054a0b4848204ad00cb0142c39e2881134a046d4e0352070380d3f382e13429eb0e18921978c7ba2bb654e964871e2048f143431e3871478a101081f3754717bc45cf35c1e667537ee229072b70b16f65c5e67d9ff4e72b7c7f2986860125277c041882342d38791a422745878c1e588e9051fda9323a6168e987474d34a2b0dec752e9c77413bea6115e8c0417429d2c588076c035e22881c5d82f0f084b4450aa01d3964892226831210f9d114030d4438ed4926c1800483199e4ae025073788782032c3e10a0e4e7c1063e4c763e1f4907643220897262e4be449002e397041c285ca6fc15c9e6c796a6579c46589621d57fb09a630c7805daea0940370ba83476b786fa6fe20507003bf771aa7c5c7c92d79fa6f72bfdf641f426749dfebee3f79461f21b318036aad4804cefc2cd664ddccf271f1392b1051f8c9b5c6d8d2752949ad8e126cc3f1c776f76ada4b6b6badd74a297dd1eafe72db9dad7834dbd6e06c87631d43c828fce47eaf425a95b556116aadd5845a43a8b5d6ba7d7d134400b78fe13b9d0029f6cf0065de065cf137208be7c0d5e3802dfe82fe316658ac16275042dffc7c391f46fbe0703fe74fd923e2c07386acf67470c0e394c54880d8627bdf8ab7f91d2cfee6518f3fd4e36f845901b9d3df6b1291627f05a4d8effa9a77fd9c35a1cd3a38e031c6cdfc157f03041bd0e65b3c0936dfe2e7b7b8f915e10a2cfee65758f1367f038418fefd9e0e134cf3feadbfdfac9749618ea1017368f12d30079c6781f349fbb4f8fe89f3333689dc87c5f74d7d06dce7e61b3da35eca47a95eca1e0c808c4370e5da0a67a414591398e14865c016e00afca0c55a7f05285d058e53490726388978edb2df0f80e6b1cf811fb4681f0784d13bf66fc0eb3b7644e1a7c518e07176c9f7bafa0ba26304077941275d883062d4bc7c1728001db9d381145d0208000f0c0078b023ea4819f767e4bc70d14108c65c3f7ee0fab1e700aeefef58322b154d6be676dce67d388572cd88b164562a9ad6ccedb8cdfb700ac592597956d1b4662e4ba6f34a65c19971cda76a1ef57935dfdde7ee6f37cca1c6a5bdad7169a1cd15a4526cf5ad42b7ab71bffd8efb1af7db93c06d57bb776a5a5bebb5524aabbb6d31d66d62b5a1943c3586ddd2e588d1c101cb975a971f591e2d219798794b302d81f4248f942e31449ed8970822dba0d3b96109ccd29725a425294b412c35594ab2746469c796279d1ab820911d7069c22587a51f5c90e4f9d76e40ced9ed4e69add6cad75ac2c812069aa6699ae6c2d968a59556ba8534bc4058660fef0be6cccf6c65ac70bc915930d58fce440529f68fae439e373f7a129c1ffdc88a1f5dc8e83f5c474f5752f3307a667e8c11769080192df063b84f07a11715197502648f47b917b30847bc22c409c70f471104fd41ef22775aba2c8aec712b45a0cd6e45f6b4bedfb3c89e2db2d5fa9befee47e346bba9594ef75a20cd2ef3e99e5d067676590b9cd9b348ef220b20e500469c99fa00069079290bb0d250373fa35b2d50cbe0df84b2b30167be069c99f99aa7a199b133343335df4d2f847bb1ebef267324d0936a404b5404b79efb1a90655403caf4d780aba61a505504e2195403a6b24e113ccaa41a1095758ae0991f5d9669fe669a70f440a6f91af0cb2e902604479125038efe65f427e40d723f76190afcc0b10ad518912c090ade6e652c3a35c59c33854b2bac3fcf1b2a518bdd5721f74185558950ee5a7443dd3a2545bec38552f26cf651a8b0ea14bc597e5eb7a639622a9420c2c4ecaf82066c2875f78237e0b07c82f6f6b5232d3627b9a17aef57f065ff3e0af55ef7a4c23adec855044a7255fd28821c720d47144dfd8cae31edefc5b8c65a64edc71655e5b5c83854397df8bb4efc9501f9fe1c57e0867018428aeb913f5045456e282cd327c43ebd2bee29f7328afbdc6b344b1e6b4c4631a3d4824191fb6bcc7dbe23b9d35f25e64a5f9bd3a8c44db62635858648008000003316000020100c090402912c89b340cd7c0f14800d66884068583618c863a124476114833114639c01860003883104196588ca800060ca2805e4112bdcbf57f8c9e468645a4ebee14164e3a00d88d866473090a756ac0482e8dab7f3c3542de536800d3e8200753b9a5b62c34cfb79b872a498712fa8ba413326f91c484690fb50948dbf73b175740ccd6434f521153fe70c891477c585cb4dc31ed30d723f4a1ee24f432e506fa8698c603f696c934ca0d94f16723f3e29f0b21c39eb48527ffb6f488f45a14ccb3d0723a521ec8deea3a47c0e2ac4c160041e00644e95f8eb3739de147bcb98d16bf7c9f9b9125b88c40f24c90eb96b24d124629b16e65f50c32246214bb1b8336ccc171fc6476991f2bf5beae347dbd88ea3966c3d6d7ba005b68ec1a71ad65d9da9044737a9af686d93974b2f5a55414c4eecb2b650b463115bc9cb0061266e8d288ae54b991df2ef9b9a206c0ebe7dc51ae10213f15f484abb464ca3886dff9b1fe1d2558936060dc6899fddb132b7ac47ab299670e620548aa13cd675d020f4b16ef77c15ee9c37eb5728632f624f051b2c40b18f17db1e92993ef3c04a241959525f44f9c802479c48780cb4153b75ddcfe51093ba308239198424aa32a5b97cea4a6f459136cb5e9dd753fded85c68c1d63fb7dcb6a5c5e6e6b927aa4ceacc042a6d62aaefacff527a7c6ac6830d7cd93a8cd09ad71bdc7ef5be3bb23b439e083d30e3dfb82eb27231804d19799a1319fbd277881354b30bb5093aea290d97a7c5419535d40f3f6631a2aecf3ef4a9191ce860ea44d86581e53a98f059d9de6563359bdd365c1eedf92c9dd241dd06213320324d6927e0c6c54945d620c3bae9e02ffe8a8a8061727e1392524f29e2a05b02fe0949787c64627a6f8d4c6362e0c711419c2d05f7abccdf5c3575ba0811fae739d0a256043c1c716f098e02f7aca805861fa35c897285772276608b30b50ef48ef1ffc07916473620965eb4ab1ec21903365550ad9bc75d6aece520d807b0ef4352e51c10fd5170bdc7df0f3978125d60c065dcc2278a28d6c669d23c97ccc997e97b415a7f558968a32a6d2609fab5b308b7c132a3e9b1ae7adba398f2cb28971c23e79f6d989da5f6cc295cd632bce5d1de96188b0f2115bbb14ef6e9a0152e34e6b38a7bd5855a567cc82d37305feafb432754f428dbe734a685d855133a2d80c5f8c30b6bee08e3df162dd2ff791f41c5b834162013a2c4d2a05ceae3a4318276b06841b0bc46f068b59b24e4b375b95b9693da445457a9429dcbb4f11486f7eb5eaba0616a106918b4df68fa82ae92cd8eaa337ec6c998b2639e079ecbf5adad84609b7fc29d3f0988e746c4835880bf0bea3e121afaca1745016df38c6c3c894b79b1a2d982a37e20ff8a9cd04b7b21ffd78427c352044a59e40942a90b2abb1cf3fcc3d2147b8945100096050afbf2182e550466821e6e220a7a0e0fabeeae8cd8125504084e6f35ca8bf7c08785ceadf88107828157417f88ba03a612e10b840a8bbca9ae0e7c4646a5e168859b6c888aad4fb35d25ff3c23c1666d4681beef2f6b7fc2d0b6dbfc11db072499dcc140db15726c3ac03341c31b1b8522539b652ea667a2ed21351e0a978732e1d03bf2d34cec22e120736fe6de21832450662987e2767544e4a154e465f42a032440977f8176ea24c61081530cceacb8af72c2ea171d3562cdda7a79b866a9424ed64a016354a7d9a6e367e21706768e60cdf8706f885efdfcf204af9db72810811c04cf5ea7d248c4dfd6d007ee14deaba4275793b3121f152e1322a03a29781886c40d4c2a05d98e37f611a446bd687d3afdbc5dbaf3ff56aa47aaf9860cc07613c5e2fde1e9a60504d019350b2e6e63d2a100d178259160d4a218148ca24b5a5263a7917424040f95146730f37d3700838061296ab76a784f35384b4e40c015f29ec320aff1df2deac5d289b6352e66033ce37790dc597fe7fa9b1de02907083cd0a939dc0bfb63b77962b860935445d37a017ec0873218291d8c113843e34e4b41765fff8a30c3f3e29ccd72f05a8f8c6605698e2ddf364abca32e776b3c26e22b92296e334b9d6cc92fdd458c78becc08c351f8b399258940b9450a2d237e5e0510a235e12522f6195b3e99858dabf8e87b7a03424dd5ea09c5e1c71f02d810d774275c09b170c08bae1122790c580688e75453543b9b863f069a9dc45dcfa865c4938218a15558fd0feb78852a6cb20604bb2ca9d0cd1febcc82c9a6db1044aaa101d3aecb8164e4a8137517eb606660753cb1cfd455bb98a19a18c27825d2942bd20da0ea74d4181838121847aec954a4ce82be256b9c4400054d52d941b515e59b4e75a7e7f474aca2d63ce48c3accfa9dab35d2c3f4d9b6a7c62c2e81b882607217df151759ae02882fc891ab0a0f8f5315dce7252bf15db21e530a1099a235e82d5de1c9bcc05c4eebc40c4a724c01316d216ddb899a907a12c9a7ae3cabe52c39eb4a037af2751ac5ea9992c56ac9ad033764cbf6886068bd559f38faeb4128f950e354f10290e94253c0c0ff0061f454f959537f54a06983dae662cc24b8c458fa8cf523465fe7234095220ef1b921bd2d8bf5604a4c98d35b6f287edeee42898fd8387583caecf0ac0e284e5ef9fbcf4b51ba1be82b40cffdfab308f0198c725e0b745b4f5d7a85b5bac36f04443b55ae6bfc7cfb00afd333b5420a4198d3345c290675b2b0d42be96a76d85b33439642d562fac7082cb46702747b92b797a21536d6e3fb05630891c3b13b9698fc65b35ff7ed62683b713b970528dd56dcbd1433d8b10d6aa77f6348849c2bbe076b92083117e3c6e454454c432269017ea95d19bd813242648d62f8585a8fd92059deeb24be44fa6fc1c2136f47d63d9c835ee4bdf53d856b433792cd719960a5880ef29e12020715c46a89f96e06dbd6932712c4a66835df537649e8e2a175c8319cf8b06c6d227c98b6a9f54f9c4105841e6d764949b84101de421e1d6341b95435e221c857358e6c0024e8a5c0628576a97d495d3dca868e30467b88b72e7e2f7772071ae817271ed87ae71c2922e2b815c493e03a4de8d7fee4c2b74a0534bfb90be6eb361285e6fdc263458b770056ae9d5734b83e47d7bb8b7c8e88ac95f7aabdcbb38947d8d706c80a918021a63f48611963cacb7c619b7a92c6b7d55d26c7704579a562260cbbde879dd27e11e4474d5b69156fdce0c8779add42549cc20db318566ca7e02c2aea5417721aa5df10a3c66bb0b1b085d685c42d6d15de38d35888e75cab2eeb26c44c477b973a6ec6a8f84c28311a10ffecb238909916859c916dbda9a86346dc2f97215af99f5c1b3622a916fa9f42dd1f205eb07bc004cc15c4a387981675173571330df3878a6a49d3dd91ed784ef3f07b2fd92e0e4c9e87e579cbad00e16273bca2ec4e40492542482178833251fca483666dd8be8fc69a69d5d196a1186b57440485d5c0a9667bc9cd2673f3ca53d27e1c1380e56515fa04ddd60c11b3320bc094730bb362b56b94488573910cdb5696c9a0eceb31a136f9acccf14aae7057bc64bfff4f238e44c0db77769c4c9365353630f7bb09edf0d2ca49a12b6571d087a09a110b629915767ef6e9c6f5a4dbe4eab70d6dd8b2638583f9353b333d2a3db67cfc93b18d14276f9cf8bbca0b93d4d2af5bfdc8a3eacbff7a650eab897a637365cf319b5e7ef555e28ddf8857f9c0763be559f46d21c6acafeaa6f7dd0c1234364e75ee459ebceda783ecd2ed106810438b56c03d15d8b19daf08cfe1e798355519ea9d08fda10b5182325c1aef1ce2070bbc1fa4ea3a0e7f769685c8b327831b198c7f7a786f204d6be3dd0449e34437094deb7e7ffc5e48abe215f8451c4b26f5b12af45046a85aa879782e89c91ba6e2255496b1205d76ad1730f2bfb5672316be0ce0d69ebd01d88481254326829501104148ad25d41de95ff4e91bc39474446bc2ef38e0f89a2db9ec8563b9869d27d669aa4ad2685bf00bd2ed7af1af28bc5660a938535c99598aaae7bdb2ad2d23c9f708a9bb81ac4c118658ee419f4f4d56e6ef89eaf6c48a8618300171e716f1f74afdea63de8f3219afc00761507e6e620abf4698bebb79aa7bb861efda4230187538a26bc8203735583b5f120060d5cc9552cff2e4b5e4695363d44c154991f7c2bbb0bf9a679b845022b133534ce40aceb2fc8a8ec7d3fd6c4b6044dc9962c24f4024426330830d041c524208e657dc2be75515fd67a49d500677ea8b4cdad10f8c4e00fd07197453fe94a50893a8dcb57879200a82a9b03bd6a4ae118072200796f2e23402bfedec7c3a1c94201779a14d3ee40914534b74c4823677b3e7e3c988789e655d7f35630c4c03fad1f0f7108ccd8c3523a6feea74e6647879c4c9e2a7aaa9554f5121fd6141f10bbb1f0bb9f55efaf5a574897f3181630b177554acf2a669dd008a15501e65f7d90cdd86a8c421349f30f73e9aa658fc2c9c84949f8f39f8275307ef834bd3113918e0048acc37acde63d52e9ba4baf177860d944d4d7dc5248c406ec900f707c7a92df0d4893a80870625ecbc474b66f41adcb864d01a1eee612569de59bfd321c4a3659a948445852ca3409cf782f03559a3a07d32ed68721aa1be375bbd8c77c0acde0e326b622259d14b9ca43de706ee43c47c4b3d0ffd153b45bdfc2ccfd46526c076f2c5aeaa3515433079d0a670596ec48460f1443595510feab00bfc2af85cb590c679a1131069ba0b1b7a3533874f6dce49b9dfab1d2f69202ce4b2fc7a58b6493c9c6396a98ff608499c8e8467f815929c232fabe621a4e39371147cb60a3992e34dad20298f014b9122f357a567a00a568fd75f1623930f0dd1a8c0de27b214200fbdb01587fd87a8ab7c8acb782069b27901bf9feaa15882e8369bdd85a470e1d0c66f98ec2dfc9a7fb1dddc9607445027a8a4c0c5fa9d19719c0ebb2bd9925ce39bc51ba1f7743121ab6f4bd1858b994c83f1588917f396d1d5fabbe66a2da29d5be99a0596ac8fc35d3800b986da0e006e2af0a48b6f8badb517cecc07e83e46c0cb692082387ea285a6c08bb0e8c3b7e483c0559d15a50c7a949b432ebd621b0af5b18220f415e30cd41c41e245b43459da8bc71b89f68f3e81547bbaeac402776b4956546594bd96dd730c9063820bd52e5840b4ba6d60c2fe8383a5b43c23bd08db0a88758192f541a8aabd46d34ec26cf02892002bd69045c32d785debc7548129495d30aa7cbb4514d6f4097369b721d7a03fa8d1a0002d0763bd02a945c181d94b55509d344b12fd22405d224812020953a589c60d17442af5192c0c8ac8115fc6dee807d1af90c2bcc0167eaa5c1f17f7cf1e5bafe2c6905b2c351f86393d034258cccca5401b5ed1822fad18da83f5ba1048737945155f078f8072057c960b777a45554a654543e0c6a9f821d28ef1fb6ab851a2c2557d0301ab1faeda578774dd644cd78f402bbe49f52b2e226da7aec539db427ce842a22f394a25e5c56248204a5c08fb1d39ce2b072c48321c3e3ba04fc062935c8a0bdad95fc83be478bf31035943b86068023e302403c7c41fb7702d21a96530bcff7106ac7c89a02b84b07fdbd80f642e78694bff054e43c6a7590addc3267ed0b3f91733b93ffe800860c63c9144b4fb6e7b53516c56e85a16aa95b019337cb6390f5c6b075fcc9f312385a1319104841bc992550baed9091459126ca61f7706ed9a824ab0c47d9205b19d4377a4596ccdda941222d8b7cff46aadae3f625cdb149b82a8264adb106c24d97ad641c8344589fe0426b2e3be760622dcc5aeba226d587b54152579b84c7e6a42b9ebd29e1f52412df92c410d99ecc00457ae97127a52294dfc79dfeb34cd1e20f5b3e2a184b226805e2173db572ade2232e353f96be06e74df2c8e7edd613ded9e252f9f62c2efae566fb9c8f79cf362bda8928eb2fd716953fcdd068b295cfc96d8ac22fa125244c1346d6e4eca4725c01a75467d90c55039498d75204a41cc86e91594980856185fd82b61e40d8dad25f899b4b6dc5666155375b8967746fcf63e9183b1b2524b1a26559463f061b7a4c0d9a5706992aa05fcefa146a5ced818932f9df63759c7ac2b234f4550ae1125a0333a8d12ce46cd62432a266c105834da3c83af606a43c45f9b5d6627774830219cee0dc10f706b27ee6d469b68560f0248692697836e2076502353f30642bc75a18aaed3b9a9d1e85a629519e4a383c45641146c21e15fb20d6f268346b3871d6205a8a8616496bbf26ff212ab072cd7bb8ac8bf0faf3e51cbdc84b808f5f2478cdf7969ffbbc751fe70a2c5fdb1da3dbe614ef596eae578e2a658d45bfca7e1b59ff7f0b65e99f13807ed25382de052b334a090e4eda2a2cdf7b5d3622db3586643706d657526fe9df489b6dc5bba7bd1a2f2a7dd66b01672ecf5111681a8b3dbe28a4223374e5b23e05f99e9e6dd944420d55aa83a03695048b69160dd8abaf544b389258b95d55870c33780d3f03d793c72079a4c0feff9bdca83d40331bb5d62da21b28badaa818d2681d026dc3a6d0dd6febcd132112a345898d1a4254424a05e0a50185ac635e426dfce2e15e5db14c9366ce9d56e9700637262d637a75cf028d1a3f40ce78a5948c2e94105faf0929b13c57af7c228b73fdb31f5e36a7d83018e50739d883b5349fd285e180a0dc4537162a9bb9a574386670b149ece19d066ba5a20e3eafb0ba8fc50d45157d458b094956e7e0869b35035a6cf822d4510403f7acd08ad0fa66f12d956234c4bbada10cd2619716ab0bedb98c0d93c82842e185d63f06efecf79a9620406c24c4c4680c95ffc3bdad37ed902504456d42fc00589c21aac450d49a652b87eec30d289969064f2f445944142a4927b3c373cd4ece203b096055dc9e37541d877960ae1c82f8d0348ba45633b5782310d3a0d5aa027897d4c3510baeca6ad79be06ccfd7b5d8e1912a46a5f05fd3a07d39f517890276429b77a5c700485e6b16057625331a4a910626d6b271c4110b4e721909a35be82bd5ab55d825a70fcb6e128c870596b0592cef20d3bc12d9f01582acf2b4f342005a5e784c4d57ce084cc5c9562275a545a5e50fc5a7438d23c96d034fd614a89362815eeb4b13f8c74e8c5b8622b0601aee9460b4dda85fac4c13e8afd68c2d3a2b217d8a2e3cb2b92b46f9074b63c74cbc6a7214aca8ed8e09bacc8b25b272b752b35a380ef60213b2564df5248c572c6792d0bc23d0fc6268bf05e65415a337909cd15f706869abbda9033c1473991b99b7db79e14b12d5c50f9698f1cb9f8ddeb637dafedafe17a85cc94340123c1f86975e254d59c2ca910366032eb5f2a323f20ffeb6b21c5edc7c405ee0546d53ad557f97ad11e6c4071254337f9440504d97a9c64c2d8d6c5aa384e9937d8577071a6560a8e5775e2a669a14d8afc0738a7d7491791ba5117ed1e8e7bb56e80e300aad80379fadc749f0861c499e8eb2e27020e4cf406c8f06bcb602016a67c10eed90146fa06d0ac88409ce9ddf1d244ef554d59a81cf751c7f5b04240fd10ed855b132722fc4b9d91b6dcf03828ffd7a197d4cc2bab2030aca6f95bf5eb4393d272407c161858c108c107b9cd85d3337bf43f2a1ae9e350e3a71163a1005a6717a0baac829659be34b354bb718607e4f4d17ae542c0343042793e22dd5ad7b438a913b9aa8ae1503a40013392ba3c5be7d19b596e6318837aac860d8421cd0be9a108f01273c7576dbc526dbca825638a16436551738e1e578b983d297b87789588bdb405a9b7959009d379df6b5fc27ffb0c3f91096a31ced8ddf949541df476f00751983912114705f2504338d56a681a7deafbe4e6a914728e8d681f0ddc8e8d58039dc3489908a6036b324bb7c54985bae8007028b63a788febd15ba83eba3d32be380cf0e4289fc129abe116f299c803b37c04174a2f1e5c17ada2d4b679c4466457a171b880f07870f3a38ea4082fa60781f3d6450bee8382b1391e46ce2a3c0b8c41a54f53f48e759c9785705a125bccefcc8c016d7b51778d765503ca99b0577762b35809240cba8c5d22b0ccaf8b7f933fe6fc5457eb6609450f823c112219a51b041d64d9ab0ab612ae3d6a8525b275b57d8ad808bdef09d1b7b400ae3e3b5480a5ebd662f1f86a32c0f6bb2470006d0c9916a34c02973ea250d6072c3bcdfe39fa51e069015616b23ec98ec9c989f01ca88c32a5cdfd1520b8487c69d9cca554351a3dd376ac1df314e031261b2195424704cad9a5beaa65b59b98210b71266074fe5f8a4e0e7fffc40c2b61c62aa8aa8cefa8fe9e8ca9a9f39eb9982cd5f7ef13335b2655a6a86d0a9f17f8129395098e44770868d9e8e8dcf5552ef0960a864ba8883f1bcdb36682e2b3704e8fa598fdefc3005e09d7b7064649d1f50ef15b51f309babb8b9878556ba961d7c25cc3787fedde98727943e95450d3023f230524911a25538cc9a4e8f6818a814eece236e1875e6696c65ad92bb91ef66b163a670ae3224084077b1ac9195a0466b121c7fc47496a0fe3329c869c3e78a330bba28eb716a1aa67dbd491999b7c86df7261bb3c57bfda69655405b341a37329974b8cb9b377189dc9b38fe1049e9800edaf030562ea5ecde545336ed67ed481b3773f1be58cf62cfc2dd22bd05ea1525aae60c4739202a700d5dea2228ac081dff7ff09e86d3eb20e38801779e15d77a10c16d4e2b5ffb60ae66e2f372d2de55e3e896489db62223b96debd97a357dae1735bbd87d9d65aaa999a91e8d60e0d8bad7348570110c1c81311acf661d489f3277f4b8d7a27cb2b76599104236ebc978a17c2aef97345cf296107edf341e559615d620fdc089b73ee2d317119089b8e2cc96865f9eca2620bf75fd968d8814cdac34f8f3b42c01f2afdbef6c47a4989895165fbbaa7f203342e8ebc9540289192f82869cb287d09e82082ae6b392934e652771a4a1880127bd3f1efdd2f3b94d628bba13314df9d5f5176c26ba4831a5e38be73208d47f5dbf631331459a291d7eef542681e7a78edfb18998e23db53cb1aabe7335b8947a21ff6d0ec8ecae0696aa2fca54da90de4680e6d047be78927123ef0bff5ea0abfe2f6c1a111545241eb9e57b905d21be8c134e9de3a8f4481475aaaafc249ef88851dba52e12a170a644fd7d3813417ec02b471f12e2a56ab882f5a71ed4872c8e35b704367c72ef3bd66c424c0c3eeff45c7ff0ea36f9772a5d9fd4ec53529fba40fc8c9c553460d2652cbbbefb7210362fc441905a31b4e5e108d1ce2e4337b2e7ec2d5ff5e14d531a7039226ad3ae2f7721454b3aa6dc319e052a94b6c8514dfd5be6fb6ec3f600b8bf7bf9e9eff31bf69daa8b7d9fc039989dd2c7ed6a80ec207e37767ca61b23207463a59e36d84d940fa74851e52a1cec19dc5f0d3e79b1118486a985f07aa17eb2d7d49bc897ba753c9a1cd6a61baccf9f10540da614eee0634023b4f9551399f63e912972d2d6837ff866b904232e1771c4eb1a4875f1d7640b0b7d3d01879bfe16f571d80ed7107955c1c9e6a693d5f0629aef5a7163e9eb1729bd334fd5df9552131f80d9e2686b5fa891a3104dbdd7b39d3fb4bc88ad42a57a3554aa5721a57a152ad557a392b312292d68a554185fe9d0c2255467847a3a557df3e3d49946b96db5bf50af40251ffa538d9f5ecf2035d97beb8568052a735780520ff5f4472498b6ceb8da6bb41a29d7ab51897b059510dc9352d519bebec66a140757fba5f82a2aafafa2f27a052b7357400906793aa50af3cbe99a44096bb5dc3b0c6e52c038a4a934c31caadafd19ccdaa7ae2af2b0821635c957516c2a2ff838d02e95f11c52adfdc810265c3bbdf49e04f58d421832d92bb4d8143e621021bc8362be7b26a0ad7b1dc8b4b456336dc28e7d496f2f630f5e9a69ff3dd4f9f0b58d07cbfd6a69601575aa817791d9931e52801f298a656164fef3aa503f3d4d3ecfba32a89e202257ae8c18268808dd56867b1344b4b856c690cd050ec332e2b070335efdbeee6428e2bcb71cbfe8f433df4a7a21843b5e136f7298c2b740a7a0bf00407e70888cf985a4bb1c1eea868398a0ac4061ad6d3652c19b2e01eb97d9894f19dc3d0f8370e99a53c35d83bab486aadbc38bfb0989b6f1400e4b5860f3f1d2090753247db4a6c7fde21b2b16926d4bf099c1063863d79621c74fa3c2c53190906fadd8b08f9740640e037961c546507ebe9c886322c30ed811862a4f8197f4e340326e5af80a85bfe3f3f0e9e672aba1ce947a5de018e31c3d369b8a814f8d28d004801f102916b67f3138fd21ef237354ff5bf9d1a27cccb19be92db9ebc2aed649607a2431c63e473fba9b5df9a286c76e5a03df1e7de8e3cff05329862200f207e52404b0cdd2b9f2a10f268b1af33f3ebde74240e4806571c1392ec7a765b505bfb2801a58c62a26c17b84a1d99d6033795a3a64fe00fdba017e5cc8bb0b023fc3f1cae3b893fd60b613c4bb29c4bc0e0f47c8e6895cd6d88122e881e25cc98b1a2b4ac05feab897185ba3a152716922115f7dab8c58b28242e8b74e3414a3c6d538d7dfe79217a2ecabb74392db331caaf11ca9a4f067b618609f9416f6501a3f3a8643809c38f5f6e519d9084289f6614d7da7f709cb7e32be85e86b3f165fffe3b3eca2e737a8453ef5f812c3591f36461fbca4f85e0fa2155d9ebfccbe48bd4a5f4d5fa3d4cb494fd43df9e22c2657cf8fed0ab0f3ac33b3ed4f9d1a2ec903775156f95900a23d38d47ba13228e4c6dcfec1f45b45b03962896265bbd544b7588e360dcc0159de917f0c59b1d53dc32d727b77c35b8ad24a46cea266e05106b9459d33ffbcd20d8214984ac9780f24d86809e2868b229d5a431c0a114a534f0d1e95be03cb8dbdcba2f5250b00860ce21d08a25c1c5ff0c172efbd9a1f97851ebcefbcfb1dd546389f65a7ff30e9c1b0d7a9687b2b9ae9790b039e10777f68212d4c8e1d3a96ee1f13dea1e01735fd7182367731f849b2c41c20f0bc5146c75352a1468390fb4cefea930a49b37f3617410744608cc37b6bcd2b1ac29061db1a316edb8a6445f87ef75458451efef8538bbd293b21df778c1c3f7325a1be87fa0e0084fb32f452d58e72510b5b6e79fcc454a2c0202d86942bc1261c6b5dbc738b2807def6cf0c963377eb7b5c6a87bbd36a036402e17853830991812a7ac005b351a160a9c9c00e4d1f6f2a73cccc6c5813744fbe2295932fa6c85d0bd349b0160937fa24e0c883117985a76b0da7f26a941e5cc7785e3bbd127a11f3adaa6f7989be12d908c760a0706533a0752f6eb00cf7876c4a57f1166b79445548ba3e9d58e5838eead1d87bec329315b70dcfcd75a6dfd4a59d98773257b8ac245adfd4efaf8cabf4571644fec226c1e4c274020ef4ba180771087062558117f4490814d4716bc7dcbc89a92069d846158e494875c238efec61d875daf571511f3a5ab597ade159d25d504988d0670cafc6282ee2a56d4f189639b2da89bb37a9cc8c62b6d5813f8f64af12bafd48c64daebbc44294a168f5d43ba6809de26236220ec637ca768fba7854f6f1eb72e58071bcdbae1944be2fbbd5d985f909a03a0c4ef51c6cbad78df43ae0e4c3c3a4d2e8189295d5ecd5a447bb49a3ba2b89199b8e4aecd62c3f3310059336b0f57fce5bfdc8d73c55207f94a88672ed024abafc524a041817d3e49ddb1f27f9d660801b5b7844a30aa42ce2f614352df815ccc14cbea1efd0d4811f4b745d11b7b97424d316a03a5ca586af4473b3bfe50a2be93aa5674a0cd5318251f9a4e498fd5b45caa236e11801d907a006e6e82c2f8d86a8ac2d247b0b0cc74a6e845034041a0264c409b8fdc45d3462865bf13284420b2fea8851b9da835382231f7817f6ad458a8270e615ed39611cf39f69fafbb6ff925cf3e2338332c7bf706bc3c602d5ef35b51ca5028a0255736c063685b9f39b0bf8080bd19a8ace99626ae1af0e4d1f095bd94cd753f3cea2514fdf7ea12bee72d35926419554149735dbb6bb9147050fb10a0c60789ad466dfc302128c7e2b9ca5e1b9bda7b1dee47e24ac3b7a296bf98ed5682c14de6c5cbb86405d41efe66b1d8e4bc501f4ed98a0f79fbd3da5311afb266c32dbfa7e567713e8ed2dab354537875cd934e7e05d98b10077e0184e094e848e7ad7d089f9257e11da5dc2d363031d462131cb57414feb2f24b727b7b8a263e8e3c6ddfa40e0a72b4cc0d3e0dd9ea6932c756fd4225a10b5fca2848c778412949233736beb3f6b50dc571b86d49ba4ab0ae588ed390d37a3f1779c7776c48f9bc438c50291544fb02fdf3c4eae66f416bee6d59579c5ab98809ba7440c15c36238aa0315d232d4ca2d78dcb76effda081063eb712a08dc87029f385e04c5a50317b14e6bcf5099e93ffa846b251b52ebff839db862252d0aab4112cb24dd8449bc0cc6f97f123b5a0daa0099b01390039d71331d0ad72818e3a56db3fffc332171668383ceb91a81888cb7f7dadc76c89a7188d3fe72a04c1d4fd178606fb99265af9b43bf9c096f12d0410f61e0ec7ba26b5252986824a8a5fe91cbabe4e2b5a752569951b4dfcce5b5843087026fb934d2a582a0920e7a84f2430a8da7990b9c059a5811e654a30702454b3957e05c31268e70733df2b67163a6e59bb471357cea7d4a71a10d7f8941a1424b0de024b8461689c055a3bb2dc4221e0789d2fecd59ad6d8bc8a23dc741272451bdad84b26f233a7abb50cb73119075bada038f63a167d8f3821f5cc6895a1eae1a997c8e10290ae8667303f33124709b74cc19879c9d80d3b0ca98b65354d28ded24fa996199047d9c2ae0e95c1d08fe286fbb62f7ae34e94d77881488de2839f1d6f4955cd22d7a426ac0162883a5e6a0493c9caedd3785df74d2e3a21ad822f37868eb9eac03bfa819fac61dd4af9b03a6a03b582d520fbe4587bd6cc512e317cc288457b1a1b2689217170a32ac472212e82d6ef3e20d5a89047af3b4f308f44604983b925b089b94315c1edd220e3aabcd45c0341d94cf1da059f0c7827aefd15f9d8ed868dc18327754231a0dfe8aeba9a62987721549116bcbcccf665603e5b43cca7279ce6a83bf660eb7fbdb0d4a9303d1885d2573571ed8c7057ffdc46d37744d113f369070722181d36305fe5a1c45fe3f0e3bda99a59788459005029617564b10fc357ffc591224f19e642c1ab1998c5292413dfe1a572fb4f00483be1a21157f7a4e676efcd5ed29d3c92a6a6e1dbdf9ab2c4b9803d3084600df8a52d6335cb9a8dc1fad8d243f7aa7ae2bd55cdef8365ee7f5b37c3474862fa6882a5ee3bab067cfb2842c8f54d674f824d28036f48282af00e0cfc2399785f3c41142008f29db2f1d0e2cb2c74ad930e783328e0564046254e6fbdd7952782f3a75605a10d3a52d27938033990fc1702dbd069bc8b21cfae0d48c0742a9395b4b2374b271e0076e3f28d418e16556ad49fd455339936682e67b8d660eb804892ac33538c3f005920859920d290191e881c37d35649511cf4f92a62b97127f6ccc247cf938b83e504b8476423e822035c1a018da90cb906d97dac061c706d4b0fa75c42a73ad7dcb4236a4661ea65bbf2ef41864252c3900e80a47865c465894d3a09ac5044c3e1991ba601303f9c4e02b27558224613b11bc8531708de288d6d739829da6f2b5a0c564a887a00d24e40fb9fd23c0d6a8592892a8453a2c225f1964c3eeba098f1950547ac1cb17cb5806dc62c0c41259e252d187d5f4d5c09684d965dbf56d46b7f302f3a49f99164ea04e01b9eccbffa30891ffed7894ae8a50ade354a3b8e5ff7973a520161d249c1ddcb0e5b2495c59d6ebceb477b6b59dcc714f0a8f3c30aff10e82a1a25c1fb7b9dd9c5c7d8f5b3359f1c5c1a7140056bc2a1eb6f0493fda425896559473acd45817a8d4452cb705516659a9354111afe0e9b052830b57b9e0d7e3b4c8443343c31d714779ee599668e25a8fab27ca2cf6f9a2be6df10824118dd799a89830f31820631847fb28adec3c9260167250a9fc60e452ce924ad920d6a4f093f7713538b123d6da307a9609a2067815ca5eb0eb4fe3e94fe3938461be22ca973860ef856e9c725a4e79b99892fa13970ed17902a59a8da722e6d9b2e15d27572bed868247eb2529b7ed98552c8044d6035376aa92d534faae6dc3d3092798fc2232903421df51bbff827e0b4f78f4a8e1e963d064cdc2da875ffb45280bc4829c9e287199574fcbe648e12377217b25b3028557b9810d6461888285c149be703e936588eef99ed0b6dd2b325829b5615f35858497e6318fabf34f18a9ad4b097bb6ee9bc22657d28cbb914bf66904c11fafe75896e98957d3d2c931c86035743a51e0cc33fc37bc76fccb6ac1a4ba8f31b2c2231e1311f1563b16df6e9e7294a4fa7328eb864fa46ac15e5dce233ff22161b361d66886b6789479dc302bc3ffd9b129e5d398f6a4498182f9a5246493d1d18c51fadcc6dda2ed93811db5c684b9a04f1f688c5e498b62d1e638fd0f42d22dc9d8ad6151061a6750f5c349889e8dd3d26884812eb5859a95d4e3c3e359fba43cf0ae145ead09ad59d8d2b30f21df62ed82517dece5b676338632ac576abacff077a50596358915cecb8d9b8843d4e13a9ba53b5c1523a97553412f127cce469f8cfb2227d3e40a0a7a7e2ebf224e242cf100714607449cde5c0b08cc1035e33e1e16af4b28f275194698530f9c9a1937bea03952a25cad9a605b0457a25ab5483f71037396a2614a79948c2199e26486135934aa1134235088494e671a5a49f44174f647a56e93675c96a71d29719bb42212d4d2749155e05c78cbbdb2cb2527e13f6105ce95ab1cd5610af94daf76e54b266e43e7b597bbb96e16624e51a1741f5276892ba283010c125a6596585738755d1c64308940a65d8541207908000b6bb3006d0e50174d14086a263dce8dbd4469379daac2273a29542dc32577f0cbd919a4dc6ab6cd151fc45f0c82f30afc3bb12ec428636679bec8adbff57edc375f5d8211fa15274ee45066f325253d41577cccbf648c3fb60f7d53e8cc3b70dfbdf2edfdea11bd11b96e217a6bd91621f4aa6678986b948e9ffe7de5557d4ef6b6eb8470c1f0ef43db1b6c955a992d77168bf2d292a512144fa59e1c6ad3b2f81e129270d4bc4ab6705dda736df89654380c2af1a586be23dcf5bdbc8ee81d47a7b4af092046fb9c854e815b412ae625101ac9629b480b7597022ea8903d7454e724e332b0d7099741f10f23fa6ee5296a3962b00b0b1211654942ddbf289198a32c71d8d8c8b4ab4a404b1b5280015bff56dd588fff50416a5f45704b648abffd541644e6cdeb5959b462045ce38c1e94693b9dd2fc60a5420e16c85332ff7234993aeb3ee47a1cc2a95cf416500764f520060b9e91f477310b3c0a3da56b474943bf4d3f98fc68faae5732daf19228140000d48f38948fa21a05c36cbfc5555455eba7ab02ad792fee9e48b3ad3db6c2a2c8d6f0d405310c1f49fcfd22b2ebeef533fc0b0589bc30767fae83a835fa09a281adc072578cda1ce38ed0c1b9193750c33278b202f0236a61c13277b01cdabbf1655ee9e063b14f2282f885a2fd47fee865f266a939df1e75f64868235bb4661d2c6c5f48b7aecd565953cf45bfa737738daae583f1a90b6be50c4369e85b25175cc6bf4dc620471de0823d85ca1c5d1d6c1f5602f79ddd49d1864b1c48498b672ef32720942120942e9adadf06a70eb127eb28a141d78bc9de524ef0cdade3e264912b968bec88cf77a373da423537479af0605325b0d76022e51a26e6dfb988ac96e7454b3bc4eb347ceafc13a46196e616d0131711131da20d2eeacf47f08dca2d130c120395711b488a3290fb7c013b890e61cb18563172482e34923047ec48ce8d07ad79fef15bf3b2069ea00edbe74ae95674b8afcc48dd4516b967be5b372f1ca3e1db2f9917f977301eb6a7e3f9ba255b066bed53c1dc7aa66bb1ee3213e27bdaf8889f24a3753000f7a654c2ec8169f643dabfbe76651bbf211cd4ff82a2cfb4796e21c07a1efae07cb527ccad21a814fc935c9b0f6bb76ee2fd67681803440f05f9ec00af2cefdba9dd0cf108ac6c96833aa531f3e54c1e964e9519081b4931ed73094aac6eb31ab46e27525561813060da18ca9bb066b1bd28246a772c4d659c846903c1dfc88e1d610c508e751cd6fc11d38e99b4db6e740418a98d54660822a60d7b7012d11d6c1f2220a79630edc3cadbcb86024fd7b6ad73900c0e3cfe20a6fd5c363bb0a427ebc969176ee5a38b6b91076e0f2671936b05d5f5ef464e586e6647df90befca0c22f2f0b9d3641cdd6768a11fc399ed1e075d058e874c64d551b6f55c074a18c1cb5f76a32633883d83ab6789d9bdeaabda75fcb221db670d506169b08d8b841ee41936111ccd5c280e5202bed4bff64630205e4e0d3d9dc238c2d76a31347efcb3fcfb569e5051670dc29b8c3b9b013096bcb34c0aa39e039fac5832ded03435122034e64a184cae662315777e005c4abe03984426981bde9c4ce21b8dffbf678dfed7bff3eefd3f7dc88837d5a440190f4e7105996bd562668c7adb9a190717b92884f0302d4b265d2d8b31fd1fa094f4066d6a94a516071d9c023e50429d157ec47cc00a350c584445dd773d4d7808343bd8be1d6a9ab7b9f72185c1db20698f70fb176bb2b52ad427d9ed0fa4d43461c538ed3892b517a1470dc735e7c159244aad567670e6a08f7c809b0c775496b6dd186d36b30a838bc5925bb61bff9a7561840bdced404dd2e335759c6a28007ba23aa157f2100dcfa9281deca0a4dff29080407e1d4fe227c41e5741befc58e28b3a819882bb7d7e484bf3a63e9fc46b5ba5bb3e2170c9b5a3568e411d7af8bdfbdc3e3e320a78b58c25926c8b02c0b8f6ad3964e93e92ad69b38708d345bf9f0c5c0920ee04859ab2008985ff939268949490fd345cd22cf232a697744dd712681d6c6a59d70087cf9c3fb67a984ab4d77dcbcbeca48de7fb5165ab045296aadff891ac65d81ef334e6a939c3ff32d5abb9a93222e692a4149dd0c2e462db44492b8e708e983a12f8ca97a9f6119344c84fdd12d91c0a62387f2ad7b0fa88baa8c756b9b3896a4146198cc2022c7ed5d234cae197ad45e5828b751039bf5d21de1aa0fd64e3d38f6af836b17273eb2826cf8299254e4aaf65a21b8d31bdbc0dd5c0eef07f6edd3cfb879088461887e0d2aef4cc7aa7cf6935b0f5515e8528f7ea7bbc00c6b7fefa8144cf126458f7281e9c48423c6f42f5d3cdfc3193d2a23ea422cf67ae2c3898db75c4f742dcc9f91c8fe0fba0b468f488e59406d2a7d995b3219b03ce6bffe04117ef90b4f473cdbe87b8ac364332ffd44d2ea57b06aac0022f7ba3489470efb8152b40f5e075952a9de4fb944b874ee6b65e89f5a5702cffcc476192b726aa406def7714e10bf4cfb009d7592dd887866dd9ef2ce21a72aba49ebec231e70aa0d5f09b4128173443da5ff407424ae5dda1b2dc70e5de12ac1b76d0433da77531dc4e694905764c16574b916aae06e4f40cbd1d9c24c9435d169ab75d2ea9ef35cd676804e47f938da6b69daed9b38f1b18d3b3ea3ce4e3fd4ce16e0a818a6ac13651dbcafbb63c92d2c3173834415dc88e785b7a11ec7ed1745449134cffab7a2352154f3ccd2abf30072c36f434936f324d5e90265c8f7f08337a42962ceea3c0f6308aed94a7db1ca8f5625264b17e80947bdc9023354bd1696214a76f03e64d7055cb3b65900acb75e16d5f01063469b4eb66e2c4c5539f7f6f7ebe621961974811621c9bafc1f4d884b4b04bccdd8a4ceadff3c91f52dd833650dd927cf0e14e4d22193fa634dab0a494b4806873cf5de994e8fb22f935db2b028e239de01874096753ce3c8c4d66194609aa646f5a901bac95607bf36346102c555354e2e337b4412a5d4ce2fc51a069bd254c32a7fdda7ead57ed57898440d6aeb8003eb5653a91afd7eaa8c5827780d0299abbac7fa01ee0d5bd67775d6dc09e9c9633f93b59df5c84a178a55e5c1a0dc93b5cbc70b09025cd9d6b1e9aae188c59634b15bc49ac7cb6fd90b5f6b1caeadafac17ddffc0b196fef5c50d3b1284df26c1999db97599ad77a75a6c380094b3b2ccf70647a5c3821e4c28b5bb3d75fbd8b53495cfe83e5fa267a1ee13f9e4d64d40281f9f7e109b27bf8e79f65ad16bee810349f2479d035215684877bb42038107b88c08001964ec64b52ca1111a276643c0ef2217e9552d77c6aba34be04a06c2a6c6978303470e1d1f7474fcd8b1c3a30e0e1e7574f0d8a1c3a30ec859c824e76a28dd108cf4c390ecffa972604d596bdccadc0603cdc309f9f3f71fd78889d36a16f81f796d0e409faf176c7392b7c77cf28c159cc7fde02d4139a2073a4b1d1b4b11d3201ae7bb7c3d354700a938e8f8c804d469fef96971f573d8f9b54806af1b7b95bbbf003880db8b64b7ba3921072a9838aab50588b425c37473ec8e6d68f29b7a0c685b70033ed3a30cb6c6d4fc160eb0cd1d6829764e86d5b681edcb850fd1c37302ff96418c126155e51ec37eb6f3a940b23dedb7c167564d55d70615167f6283b5c4b6f36da41570bba7360bdef64b4cde8abaa0d6d3eb44cbde8971303ff09f67fe9f660f4c890733249fdd0f46b34bed505307549df091d7b908edc1a94f418b55053d3add81738a443df08f7904e138b66153e79a17400799048d3ddcd1e019afcce43b2fc1628c57509cce3fedf8504924104b3236f2e0d5d0381056fc2cc6b8f554a27f686877e5ad9434af1836c5817013dfb0c36edb9cefde21dd3ac78620ac66a273410813d3041dfb2475be2859a01ceecaf559d7a08833a20f23a5c7dcc9fe2242045eb06275fd6f6e5ec11a5e2fb7c19e29c6c0c9c30e9fe94a9ba4dec96acf1e87104dc54c8fc8e5d61cf13a7c2c95a4f4900cf0a951d0cab657a0fd2d67d153b2135ce0c018cc496ffcd4559b35e2f78884800055a6ee0efa1c268727db93c32e86d5549341da6212289bb800cd0850a6158a5b71d80712c8947b5419768e87d45b4af4ce690361e2ca029c2112dd3547cd125970c751c1202d1eb2726019b27e8979948800fb0b8280996039c61d6d3782135a0995f6fc19059ca84834acedf7d43bd0932edadcf660a31a4b8789d85568c81222098d0d6e4a4e8a6efafc290000b8cd40ca8e33c76a7c6e25f70433e4836120a2c90f1965adf8bac4c05a0ef95f68ec336097263ad0125b26ef3a2ccf881ce1eea2feb3825157eb0dcc1539c31557dfc2c4bee53b4087b5d9680a2decf713d59abfbe7a3bbc856861ad1b5213eea09a81b19a6ab1c9466b4ed4454de0e135b486d4913262f32c5fdf93ae101fc2fc4ab3e6227c1eaf8684141a2595303003ec6d2c517b7480e6117c3bfd3a2492be2412ed94fe9c1ae334deef1e632057ac017ab2393b8c405a3f9ed6c0fab66578a014ea4b9a3bd6da2a3a3bd3cbeebdded32b13f47733ad5bf448bf922094c9f94e743737d95352c5c8f2ec3bcb48292453579689ba34bfc418ebb6ec4fe348ab75d9af9d7e53620a32d637a7fa0376ce26ba3f2b72e3ce40531d5b8ed4e0464c42ed261f81e4cb26f450f4c52a35317b51ddeb0b81f814fb6179449c0a9ed91873ee9dd41f6ee2b7d326d4b058b59c513ba9127464591b3b61191a67d845e32c051c08d85b0224e96f0398a22fca6fa3579af550f5219bd044cf29d37f863f99f96c759c6a4cd347b220a34a1e9f8a1e14349846096af41ae52c344db2400124584300d0da3198b3ef5f73d6d4751c6aa544722590f2aead95e1b06da56a624e9761e6f05ad769b0c4916935fdcd679b4e0e5ee7bc2b46826bd7a067c74cf2016aa1a52b2898d550f17a5e4333073c09a4b662b598e1a320fe444f15d530508f15a5e6f33584345d1a5ebce2c5296b86cc5d1a6631d028eb16ab71165d2dbe0c727d4b45261534a4aa4db68a63d8f5d4a500a94bc332c8671e535c85fc886c1aec50f48ae69b8aa21e254139d93f83284a528a4205321083d2a561790a4fcd6d52a22520c198ce059e895643b84f40144c11db08c067d448f2c2b4cf8cd097fe77629be2ee734b8715822d0e883ddb171d5bff72e82415f6aae58bad854dfcb5138956e0c4b07e86f741fdfb88a47855a7452a35126195fb83223a7681f8bbfcae2085e1f751591006c1c21fc1807230253ab628732901ce883278acfc23a2f4678ed4771db0eabc93da4f824eb9c9c8454bc34a513271841df63d51b8a0ce060a664da38f68a713a44b6e55192e03d72821293f1954054163094905b8beeb0972ec6d1b05f10f71ea62eb7a5cf4e0608827a404a9214ad41973ad480a16ab2d6e0fb1d806719ddd99fdd8f2f103379f14fa7dce35e810b6587b4dd4cf560a5984660e70ce052b2e995392bd2030c15708828a9a4c8dcace7d66ef4d933dc08c1152d2c3201e9ce19d957f29b795981c54a5d2c34883a803e259cb6068f45cb4c963b27f4b0f0fe4341fcaa8047f439872d93874a1689b6e31966b8bfff182d07ab594327e2a4597e84efac093d268e457c65a57ece89f5289e25e468f64d62a2a62564894f86bec576272918eee50f975363c6f93fed081766d9e37a5f4f3bab6a7e4b50a442f84471b336546fd801383c210a236e3fd040b5563d1ae129b98b8f9a74cde545a3938d318460202e89ebbc17f6c401007c7a50850c82b9ecd221232e4ffe6cd92b7b4840a9a401822af9b93a728389f988940e21470b10d7e3a2d40d4c7d4e0f4d1af44d966f757f3a4ea06606859b21b9589fbed608bf37aaea8b007cb9958c87a38053ccb6c171a9da752c836af5674bd72e194773bb0b05239d0716b2fe49dea0ef0de3492618d18d65aa248cfc5f4100b1f8a0a8a00dc436fe7c24a676a740ddbdb0092c5c9ac8ba0ea3b84196f94b5d6c9cdbafc73200f36c4a4620137f9b99386b3ea6773055d66b127e42943705231ff535e0e3e7950e285a2af601cd56b7f7466d0f58905b0d334a93cf20984a3897ace509ff257c6c5870efecf869f95ed8f73889de3539db9a1cec552ce60aa7f90c7c6319200c6e473560e99d57d35abb05d796926464ddaaceba201b6b12e9c21e6208674b4db4de5c2635bd0860e6362b34540ddccc5d2fc8d0dc9e5d5998b4b293d1092f1536d79f64e47b3f5b5b8c46431b6fa4f33a699f3e8a293dee177a7ca44c5a415b35883855b1b02e9e1486234dfc793e7886775b6d1c35db1b4237b8f23b18587c61c997e0bfa4f183266de02d58235acc884523f06f5ffce13151650970ada9f493b8b212b745e19b2d45da9c1910958fb66e462f3d00ecf699b767f10251e2b702098a71b6b7a00cf8e30a3d4ced30c824d761d2eb758a9337759236fa774c30da95c88194fd51bbcaadb782b77c53a6b519eee6beae266cd3b327c9f0696db5c216065ac081cda9aeee84c524cd762b7b85b9d3bc7872b77070eb8893c8866cce8c8738a7287644b62650b7619db4c06f48d940e30c9b428945965e233310937dd2c6c2019b8d57262503543ac9f9142a25e143c5f219cc56eb70d80bf63b08b3cbf9b79001dc9b7abe9e4f51dfb08da07ec99404daa5a4f70b963b745d96dddcfeff4aa6250d6181eb835c7b88c734de58896cb2f124db0e27d45c62c4ba33a9010b1d2e10979a141aa5cd04cffe1513a7d9411ba00cc3308927485b7bf760cd99fa964afcf0d0244521f21780001c14613d2b45c8cc3a6969e235d510693bfbd972676d3f10d817b9f587c1be8214f96402861d9237a4937d4b33cb5dedf76ebe2c16fd599063f718c1d8f9ef3745671489acf8b24f4a66098cd10e9509a4aec897f1471019466a974d2d85a08f47ced0748af8cbf03569280f5d8727c42caa858189aa71c32b67eb4fd9cf78c4301276623ae83045d6cd1f3c3b1e07b5496b319c90cab20b6a01f22426958bd5d44e14eae3c88b797d872f57a5647d2b98760a7fb55d5613637b6aa16f1415f4b4006737c997cec8b04067e00ea03b4aac50e76a55123f2d55609d8db39b6397c9f5cb8bba9a7001ed6b55c7f3d24c87af8ec46f70a38a8eab70421b52643528c3637af232d612dacbca6b9640f9ae63357ecf9152b2c88d330dc6101ea7be5c898dd0bb703e8bf8bb4304446da411e912b33fbf3a939b7a61ce12474a161201102ed5c0304a3b15f64217fdf66dfbc0b0513702658e83f0f17672ca5455756745776c99d3177169cbfe6a107ea12850573da982a2b5da50acac2a1429c659f07b11cb76b62dc1e92cb2849fe0e261f7acb8e25aad23bccd32c7b869870a021871498504fa428cce4151ab2b851a61866a9e36abe82c092bce38e0be9bd9a4485225c510792c96262b6ba84594d5be3c4b8a24c60b1f8b9a62a08e9135a18c1b4a56ae01b31813841010360349040ee060db388d5f45cbeac32437c4c3b19257a2826d7121ffedda13697ea577e73bb8ef0ab98f286d4add176347422b057dbd9fb4db3e460131fec163711949f091679d94ec0854d78c8adb90e33150184a35043f73357f16d636503e81b8c9aab2b3db09f29e41f3f081f6268f8dc2f60b5ce457840f5d10f627fe06a163283ab59e4421c2b6c5f5e4566ab828b952aac2b1579ac6274ee822d9643440e9586a22ca1f8bc76102556b05311f628450449406777fd89982217041d44abac287a627d71eb44a97144215426b8cf7f59c8d1ea842d8722f21a4ee16d371eef36d2aa0ecf6c1e47d2517ad9fd8fa3a658e6ceb76d5a33a4b73c4ab8c6c91482b42101faf9d984036c5570e291e42fd0353a75ed3c1a44edc45c788d993e4fe747d51ff2f858bc6c610bf275e63774295f208019ded05182c570f6ba115b0cf011b93dee452fa96005e3d57135d72efcdc021264c19a53008b3887bacbce2c49cea39d70ca6a08dfb8bf48589d6bca93a5b12bb4b9eaeba8ea3b05aa45578569a6bccd6f66ba7455de61c69962faf947dd69273f9f1c69b14ffa6c93269b7d21190415d161fa2047c15072a16929e3535d6afeccc9ee6d773e95afb0d79da1220614abb0675df431ac72bb4cf37b5eac2c779c0646cc3b8957bca10185ddab63180a8798b64f3b817e2ff2df92ddd8cd9a7bb0506901ddfa887668c5191578d675256f73395991225126b651fd587f99c061e41dc2b7231377c37f076a8c0374102a1b78d4d882607d1ed45f8d4509fc3602004b724dc273b47c03d71c6ac9b8de203db4890e822238e372847fbec42c7f149e9ddb0e5bbaa76e92f1cc2318636b2a9c2da69c00bfc0ab2cdbc8c2849f59f03e6104c3f7b80ca6395039d652b840eb69d2af613268d91252085596393d122dda7b3ce167401f8380ed18aff8da6aeb0c748b5ee75a1bda1e14104791347d0abef308ea850ed1ef5b1b2190c1f05d61acb62421f43b040dd3770fa569acb472034a82967f5c40941da70d0e7da867cd8e79cca79c348fe1448b2d069ba0008b1fe8fc5ac0f5b381b323d8478de064b8c37d8598477110c91c446dbbe726179605d6aab2022eea1c4e35b1d6ceda2d3639d3f283fe9b99d94d24721389eceeeede01370823089f073487e6bc9eb335a5e0d9ba66ebce969dad3a5b06c80638c001103004012e12c07a9180172f64842fe84f7c61baf107f57f3aed1de3c79cebb9cb6d593fa7c7c72007c1e4a8909873a914132323f3c3511b7f240c9a24a6e22f51d2044888c6d6954bd3d09d6ef4a709ddab4d065af8c5788374f835e20b9ac783e4075a1774f834aa6ea625c3c2850c3933d8950a90cee5528c0c0b17b92587584137737ec01595c14bc103aa31e891d2ea8fca8a5351b1e9dc9e52c59d62a5c31fab33c506e67b3ae55c3f12de57da44c88025e2747ad414156c2076141f4851440a2152fc90e226ca14517c446945018af213a58b1284283ea0484841c66006077cc41286c832c413569d068ad091810c6c6670031d4ea7474521a28909a0184187214bf41cd18a22074620c2942b186912854da784472f0786608207258c50c509ddcb0ca8f460052e10f20108c94d72bd2a3c47e8f6e70c0eb7852397b4d6566b6d10d65ef32ffe6230ac5a5b6bc6e3b2b108fc17dee27603dc3123c4d27a39c717718b1140912d1f15644b89653b589dcbf17e3a64b2bdcf5c003adb7e7d39a7d470521a55d7dcc7146e54d7c87f0ff5eec3877bdbafc59d41e5f3d7e23eae796a09dcf0e0a169c0d7423e7c2dee0c7c61185f1746c38c4cc99fc1ae0b6a1ab29ec09329081fc36aeedfd7e272aa7e1f630d2f1ae0639de3d67a0257a6ee475d0361bcd905ff7a8cf1f597a631b16b31ec311d1d63d8c38e6d9c8e7e6df7b5b01b8ef9f32f9fefbd7865757bf4318d9c9bc4a8809f0c175b5da282ec1e28aef4d703058f01faeb8162d57b2210bca3d78ddbb1e247fce545fcf49bbbbcc65b3ee3a6d7bc645bfa7cfd1cece9f2358f41e614000f764e0c72b70864a84b2e02812912bc18b8546fa137ce42a07212898ffe858f465cf42f5cf4272e12f1fdfb5df8fe16ae6daefd0bd7fec4b577e1dab7704d7b4cf30a9efd0bcffec4b377e1d9b7f0ec4d3ccb1ed7bc82eb7fe1fa4f5cbf0bd7dfc2f59bb8fe12d73f39b637cb7ffd0a1eff85c73ff1f82e3c7e0b8f6fe2f14b3c9278fc118f2fe2f1378faff1f8198faff98687b8abb37a3cc5179493b1e1b588dddaaf5fb7d7630c42790492f35a482034326607c02c9db3300ea89dbbdb7300c551b75b04ccbf0eb3cd2db6ee14f04305d9f0317d4ae964a192ec5a6badb5d65a6badb556fa04c6a820fb955041a20e1251188912248a28ae441145141f9789095080de2823adb51ab153b0af9ef844c783adbbd6255bc8c601c8095b89dd832d53c5be74ca080548e7503f4e600ef688511953f7b5e0a81f36fc1db4c7b8edd782f38e838afa9c74f6de120200f4baa2d753af2ebd65011d5f177ed1e9764d9d9b7326e20ea1ae0142d95fe858c618a1c419fcad3f4d23da9b09d0af00fa95e937f200747ce5c8b78eb11cf9f3e9184b29e3c338938f44b79a06e61ddf1cef7db65e74de1e77cc58abb8f6b2f6dafb89101ef7c950eb5efb640538d7dd68224ac6131c20ee131c76b055702a141f6cadb5d62b586b6dbd4e96586b6de54e43d84e88b841e5309fc40093f264054f709ee0e0a48a13284eaee06489139e798b38b901bc9c10d1e1632f93c40ab3db29a262ae4d093e5c4e743e5c9ddd821e55082a9df0b1c3bcf7de5b7bc4a85badf114515dad486c177a6cee843921c5c90f4e6e3aa7c22c7808a305a6e273282cfffb990dc2d8d75f8c6bbc2ea573523a679d3493dfe3c6c08ff11b9c9918d8cfe48bf94d8f9918796ff92f543411c2003f73157ea1c23a8639628927a619db2be516ffda12830f67f3e3c9d431bfc9417dfc2d104685a9b7f231e62f06ae10c68329aec214e6d8f750c5c0af5f4688ba1f5f755dfad210c0e9397156b7d356f9c4a37e71fe5a60fe17e657a676aed7b535b8cbedbe16bc06f7893bd54491e3783f9b08fcc570ffbd6a2642055cff70a9677e63ffba9f318731eec52dc53cc690a59b1e1fc3f410f0341f75cb9f3f18e2571e80878aaf3387f1529101f7de7925dfddf2f95abc1830bf62dc183606be19f31a035f19731a036319f31903e38cb98c81b3cd5856455df32b535b7319036359f3180363f8229ed9d44a3115a36768199f9e59d635c731f249eb0c6b78c317f7cce663be652adedcab86d98bd46fbc3350125f0a52caf85290194096240690a505107490d849402544278014e1840c6a0ba8104ef870a2475015579094fe7a829a042511f4b3b11954a4c39f49e0010c3d61e2c9139efcf4d7f3a47b3284273e78c27a62832742a840c1e95c7e62e889a026fdf53c91a467c627ba278cf4d7f30491cef1fe9e20e28920b3e7091fb9bf9e276c2eed116a01c12af42397908fb04eb0a811ee6646ccdca04e88487f3d4241fa13fa21c483ca0912219b0e3fcf1ae001484101fc0a1cc1086bed1164708bb044cce090841ed6367144ca0a360f7b2341060bed41a7a78921b909204df8904c5ce95cce9309a1cee54c6f0e3cfba7634287891decb0f7e999a8020f1157b07131050f5700f2039ccdbd0e08bb87dd84c80d145408eaf03354d2b9dbd3e4486fa2d3b9d984959b10e1018fcded28ec17ec9cbdc40f8fbd4fdf444814f04928e5841d6e2b5802099c258a007197c8c10e76891ae0d4255ce0035d02053ccc2570789041d8dc89091437354e8f5ac2a773aa1f3cc445026645a2cd81bc3598d805140575091c3b24c5bd9840d1e107713101ea21677250b960a20427e652dea51f5bc564083ac8e430af995c8a61c244e764980041264a60708449c764085307263ae820a512567890c94109272b991c9468d2432687c9c40826423aa7fab136413f8284f4b7e988f09f0b2ff41bfbb83e9e2fc6964c3eac09800fb71c5190457fa82e75cd8a2ee78c2f52fdcde7808cb20818b02b16814989e1d333bc23badc31156501fac5b10c43af5bdefc1453eb43082194158818f22dc1a21d6c265b088d279b921131278f81524a0971806928689c1c04ec04bf80520e23a594d0ca8521b9b05c465c2bd4ba024a2965ecb977658fa851ace52f502691406ca541927b1f6ca2521ed7e32e239b2ea56c61bd163c6cae85e562d3e58f6cdee5a70520fc64b8979f1e3f4339a9a4d5766f567b2f5a5bd4de0baba4959d93d4d90bc359d7b47408217cf04d086bbdaebf6c707a7c7ce3a3c7cf38fac768155171dae89a964e593a458ce8b0783a564445ac6b5af42868d46424340a8223206e2465d41a01c115901f4180ac84e4ace0cfb8ba233c488e74939484f443ea48432429a42ba42152ebc686d482314a59f135e17efdf58bbf79b3bd5cb2ee51be254a00bd18300e240afa7bb97a3160ef63d703a368c27b98f362b0ef25f062a8ff7e70da810b1317215a784c534c4494a0f056cee75e6ce076da418f5f5d98f4f854ba08d1e35f2d98698a3411d1e3c7aaba2528f4f873cab971f187c562b1582c1b1b1b1b1b9b2d1a8d48a492c9c65caf4eca42df418f9bc91642e3c9a66444c4a738e68876b0996c217afcaaf1c46c4a8f0f332268cc19c2d2e98e745dd775dd8ac562b158ac6e6a792ef9d5d7e1eb138382c6c941c04ef00b20d6d250e8f1a5c6e9f1630e02ec143bc12f680d459c5504d2b174464023a011d00868043402ca301796cb082b402b3dfe053b0c498fbf2f2c3dbe7619d1e3c36b85eec88f12a0a0aeebbaae5bad56abd58a85953da246893d17ae32fb9c3da2c77fb15b8dd2235c412030070e61e9745dd7751d005e3a0d7a4c02ed03aa34a83449d06deda193cda3482d0963e672a416a9c5915aa4568fa552a9542a954aa5cb4336513955a73c240c95cce95847744a5d7f4a8e7497fe62f3831effbdd8bc16382f362f3f162022cbf60128065530c2922152a8c26eb293d8568cb022858a1fa8b8992c0814e54ac66eb682a03027882036f77e8208620a2b4c5ad1850c8f07090b5aa00e9f664686850b192490952aa0543132568e58292287583273a4d0a232b0a224c7ca112b3c3f568a58e1811523ac11308a09d5d62a63ada86085552b3df870d52d8b0fd7891e75097a97ec304f5f6b95246aad35d62a4c389d1e65a506476c803d0410f6eb5e9373ef47da802a55979a86193a43d77bd1c898aea181d5ed3d94a5a1c6e27c71a7095c33301de1bb99bdead38828b9ebb6b19c75d635d7c62570c38307ce72d63466d84b84618c31d6b49daf199686ac6b6cd77ac6484f406730cbb22cdbbae6befd7d4539db39c3d2a0e91adbed6352defa224d4ed788a0482412895ebc225d63ffb29b0d7094354d03f7b7358df89ce85a116c51e957986f301b37aa288434db37739fc518c6d9f21afb18d6ba66e62cb3f7b1d7b48dbb61363466ad66b1b53b3aceb2c62ed6b4ae81dc853dd6a2a6699a065fa66bf463f75e36748dc63086bdc52eb616db70cded36c02c7673b063fd980d8d695d53f7ab41b4335d4345f32a6198c90449259dffaa91589bf6ceb0c759cbe9d4b2b7a66b36dc7beffde2cdb0a789bae67acca4f36bad35766d3470c7362cd23551943551c6788bae61941f8f3286bd0b865d74b6e5c725582a954aa5174dba06fb9cbf45eb17692c5ff9adde68e09e47ba066253b6bc067b8ca44bd796f48661e6ef18c18089c9924072d55fec72de4f24c211ec639c05c708a58c394f4cb58d6b397e7a06be3e4e5d03b517d8fbb2183f4d9458d6dc1fbf3fd23e7b1ea2df6f350c0dda67bf6b106d4fa644f56a1b10565db3ed65b509689f3da66bd0b62753d9cce535da46be2e5d932fa66b4cda0c797baf05de6a4abce675fc99aef13ade2e8b3a625f7f1f6b1ca17e12cbf45b1b60961932ec78e374746c83b181751dbdbe9c6f39cc42e7664362111b614505567aa892a5ca1455a0a8e2449525aa2ca99244152654e1e9afa74a912a43aacc40a84a0b5045180a1a3a32c4d35fcf5091a19ca120fdf50cf918ead1a7109ae2a783952589d8295653f4c05d2a429dcb41fdf5505142a5099d4a11d4109c1852326483a19c29ae4c013445478429727ea0724508547858a04ea51d2353639f9eca902a43439d43fd4881e5440cc8911703fc4d81c4eeb58826b9e71681503f983c3047ec225c0005203c58c1ce8f246c296c21f6142b5462107444147a8c00bac18e4f0a499418f0a0040f780881406670b877269762a620e92d2c59c20f8c39b125bb0957f401a182362a68a382f6a342d806c629279d94c628e7a474d6a129859b57e610bda136d89cb4da2924e7ba8ef0203972716e26f9b1f6ce0ba31363fc18736e02993f824c20dc14327330ce5367746a414d8482a4b4565dab6f3d634ed14d44451f3530ea1ac8c91f3d7e49ae4cd36492119af2bffc38c31c753af658e63cb1974038ce7a61ad8d4a2d2f30bc249aa8d84448fe534522aa584465441579dec79cf802c3b87125de441f3ffa030ac21361c0d50a2259c1242bb864d5040af5f7b0833f220cd8c12050089122fd19e9ef371061c0964cc5bf425bcae9ca4d7f0f5737e28bfa4b3ec909c97e3c2c5ee614b35d44183107c3ab9c73cef989b8dd4fff504e167ed1d6322df165ea78baaeeb4aab1fabd56a458a2f6a130d248130d0057431168bc5a2363637363636362c5ab80104d4ad72ce396795aad4552a15964097966959ba2426753c9dec70d78d56125f57c462c9222c9664499664b510800ca9c3d56ab55a7518638cf10dee6ea04baeae96e92c45f7da1d4fd7755787573f56abd56a2581641309248124900492400578215561d7755d775dd7755dabd56ab5bab1b33a4bacd97b753c5dd775ddeac76ab55aad6068015bad39e79c1376dced70d7d9567d9d25beb27a61cec43a9eaeebba4e9561c8a822dbe6989393b38160ce39e79cb0d56aedde92c2c156154273021873767b17fbcffe73f91964ffb93c8d74913f2de7647781efbd8bcb5b2b7fbafcdce47cefed5b5b5ddedad3cc4c6df9fad265e3a0c9daef16656d7679ec5970f91897877171f917973ff1962dc74c376ddc8e0eefee769fba3d3dc6ec35a194724bbc8448fb4fcaadee38817c388364afd05ed892564a7925e6f2b2e54ba3df9fc5f02761f87de14fde1b4d1b77491bb7e529ca8fa82e519153f58dcb31232f39a14c28138a295a1bec20b4034f9c57d61d8af401f4e7b3838f2b1d01fdf9f438d2f1c3f3c9279f7cf2ce2be943fa66ecee8cfb4fbffb77e3f4cb7ff9afdf51f4f6476fbfb4713adebea66d1c34d2b5ad6ddcc5566f3a66b7cfd59779cb91bbc51ed71a6e879c7f3d9783bec401dfda806fe14fb995b458794cb1153b1229c622305e9e833a39eec0cf7ec61731fe7d3d2366773fe418c3e26357fb1b7abca418e167f7331e9dc59d58fa98695f8a3cc64d478cd88643bff6d8bdf672368b39c6a80315101f8b3cf6e62c5aecede3e774448e8b5db7ff39e6fbf7deec332ec7cbc7871a02da76a37e0c6e30dadbc7628c51e7665967b1c7a6871b0e3843dcc1e2cd6132c1bd372ec7ddb0b883c596a6336b37c88aa8b8d92ff1fd2fbce52577f9c94f4fe2793f4d14bd08c2b8331f6ef873e49e618f6139722612dd1c39ff89bb704ec7dcbc85cf1c39eec49db81377762429b9f1d1bf3f9f1c3a1dae74557f3e3a20e93e39f8e020a4d573a71faf4f0f1d9f1e363e3c7e7c780cf1e101d471d777922a8434325e4f35bcae3a2f1d71e366ab5f2fb3e1a0b5d65a6bb5119fbb6810a4412e80582891f9e933c298d9b44cd7705a21cf30ce3f00bcfecc3f005878987f2bfec5e54d2f358dab67ffbe859815a716ba698c3e26441f9b5770d8bfa1d300c0d7f13537a5740e6201d870e8b7320f0340f5dbf8d4851964362e76fb2e70b3c5b970798e850c17d369a9d319659e6e18e7e05bb8c1b8f0d7d78f1b4d844304f05943a55ef4c28f08f01ba53304c517953ec631ca3feb19ea6f4f808d53bd909afb51031000fceb6bbc281669dcb810ecc01b8b4e7f445fb435ba5d9f7f064d28399c431758d753fa57d7f0c768a40ccf3e005c3fe7af6f9c73ce39e7af074514fd99df0030d3c2c6c2a9cf978d7b978d4399364e557a4a6189f419697e9fa739efddfb74fa47a15417e67a6ca6939e44fa80840187c8d4fc39da38154ab471a8df1bf7a79d376e4f289abf70b373cc74fdf4e91afd382cd533e4ae1f0e9130a612f8d3a78c4eee70e372cce8096506e119180e814486c0211b44c1182718a7a2908a820f0e3f7a01faf3b169f291bbbbca5aebbd353aad99d1a3bff5b19c6d4a5fb437fdeff4a2b61a9d3e8d3b56335a9c588650507ab204f9d824c1e9c962e4c5f4a4e76e3ffda9f42bbe050687f6f8b5cf8f352efacc2ff85916223d7b9720597c54b1eab20a215d4a39fa4bf4d76b35304be6dcccbd7b9f4eff2814a7ea7983116dedafd7fefaec479cdbffbe01a517693cfba723007be96962b665d1e3cf78d510c87f6999966999966999f69caa67da06b31f6f1c4c0dccd235fcd7c6c1ec8d06ccd2b58dabd1afe76ad4e8d773fb6bf654d780bdf49873da8bb6176eb6b6cd50fa6cc3517ad186c365f31c339cb6bd883a7d76f9f7309c9bbde5a57600b4e1b2cd883a6dbfa2a5256793c9943f7b9c99b4cf707eed33537e0d679fb3979797972ccbb22c4341d11e76ed737ef8396b3e363c3e36407aaab8b2376e47e65c8d9eaf4cc9cf2b5a4a2316fe4a182d7ccc8a534be99f54f51cb3c16cdcdd3dfb978ddba78d3bf5ec5d36ee94bd69e3beb471a89e3d49b471aa2cd39f39577f7311c0aefdd43662d71e6a0740edefc6e1d77806758dc663d7360e766dbfccbf376ea6e7a7915aa641433f37d3673adeee7e33356660e236c2c5e39180f7f0be1d64d09843a6d344148432e4b06764f04829a594503ef7786a3ca067f3708fe7069b7b3c3ed8dc7621079b7b3c58966091d26f7f3d5884961840139b9bfdc919213721949dce2f017d518f334e6a821bf7d2e59670c2b9d180fd42de616c70546e9c0bfd71b05b0b6f2f62c61720b001669918f51e4fdb2fdce363ccc404405a710ea4ac177f312ecf7c83a181a3ec72ebb2c7fe3acd98bf6ef2dc2fbfc1308c09905b0ea4084edd3e0874dcfe1e0c3a4e1dbe17f6e7ecf2512fc6c5a98deff37f5efdbab74b2a379aa78cd7567855dcbfc62a05dca491686b99ce18bb202f82ad744a58fa01ee00df60a4773deaf437a39c3f6917258536d65a6bada5d6da3869b5b3da7bdd0bc3139b386b2ccfa927076f26cf6eb2241007c99c70d5e34b12735e922844490c12893c2099968c1494d0953e5f47a783870446907c40468f51c68f3e5fd6803ee65309d0c01fb1c90e204b9ed08e1c6ec7aac78751ca39298c52ce49699db4566befbdecbd2e0cc3386335575d334d574dcb75ef5c45a25c4773724e354d1c8b963c5da8ad9cbba149af9c4472d1e797822ae758f4f92653ae9c93a1cf6fb9a1d5637dd8abb5dc0d405c0ef93492ab9078267802188b112c3afdf5601982050610cb0f2c37fdcad0945782ae2c9957905c3132e9152243e69520a57ac5c7951edca562e864a908a2a2497f3d5424a142095474fb52c1a2a20849058c9ac2dfe981edf2e494524a29a512a614948c409242aac12889d10ed3a4842905252390a4906ad0e5c351125d3e1cedd0e54329390b504a197f39b73bf77986fcfab63d22a583940e606cc4975f13b5f8f85a6b6bad94d239a79432c668447cf1099cfa6163b172df89d8f5ed033d808646c2b81153f2bb1b2f490df145842183a6befce73bca215651b551dc8e34f5c3e666ba4c15b1b977a40212c6fe217992115fccc09b9987f18098922f6bf460d3ed49b9575330bd9ebd36e76e545965d541458f453232918c4c242313c9c8443232918c4c242313891ef6ecb1d1939e54a38b7e74a38bbe728ea68b361ab961cfe4bf165e1faba692a8d78bf31951d6a5cb9f5c0795a25879e0f8385e6b6ba5744e29896c08e50e09a30f7c30b5bc7e89f9d3bbacc81e5fbcf419cf316f9c0e1af50cda5f1ba7835e5996e1fcd9e37ba1b5b15649e99c934a5963c4f9caf7b5b87e6ff7b5c8d78f3887ff7d03626238edafed859b7d5dbf02c5c2850ac5c24597a5d7fee2dc0d0864dced17beb0a67db60166b9d1ef85d6c65a25a5734e2a658d314564e7df40d7b0e8f7630e7b44917e6f2fa246a2df22d2b6471b77a36b9fa5e32be6b5d20c317f6d38627e6f3856fcf598eba09cb6e9a03d265be1b2629b11f5b23d7571696979fd34b145b768ad758bd6da6432f9f0377a2e91467f491bb70130128958d041ff533decedeef73780ba8eb92292900481038492540f288452384b8038f2c3159f1f58dbe7879c95cf0f36393e3e80ecd74200bb28d563b4b7f6fbf75f38b42dd56373356af4fd6fb4477ba77080d001c2878f8fd68c548f548f540f2d0bb6f17b5e1100a06b3034d4a75f43dd6a484d4d83d637658434e3766c2dd6c8786fb456d63a29a574ce2a25d535d2c6d86bca880dfb8d3b64a43bec3d1a8d46db7e1c5f4229eaa29a39ef88d7bc3e7aaa6bbc3e9a5f754decf7f1cca396273d0fd397be6a181a5a9ef435b46c4fa6485f8369137de9eff5afdf1f71d8457f1fc3d1e37ba1b5b15649e99c934a5963ec7424fa4bdb5e44895e7bfb3471c46187b78b36d7ff748dd4648e99ae69d07e1f731a928695a22eff051d86e57c2fb436d62a299d7352296b8c7d669e22821fe6ebc24faf5ced5f4f235fcf91bbadf7b590bfe382d2c9bd7b9f4eaf52c9305ba49717d3a251ceb07cb13b338526940a74e08a36ee06a1fe05659950b2c81def11f093c19104b18b281bbb88b213bf9c8fe5fa62ceadd6d979dd381875fadc68549ce72faa8a1d9f2b9de20e4400c4906faf520e69155bd70b548120e7bbef7ad853c9f03aa9d9ed662956a4ed561adadc03da2829360eeda9e8b5a78937e20bbd472f82223ed2ffb4c83ed4569370aba10218a821bea019061eb552a89d52a6784018882f38889262c31b1be8408e0fe820417c914d2810066dd3e477e0ca8d0d084d28da14d2d90cca28a7b70965e624ede78d5361273b7382d80d96e94fa6a41603ecd7945360bea82a9b53a1a26c1cd7d3cea97afeebef8cd4f7ef3f9d59178db49b0ce62816322816f1457dd44ea1164565d7879d53b188a8281baa248d8cb80361807fd364463e9f9b255dbe0b19e20ec576a3989671d7876047b7a5b4f51282523a5b10ba40b1789953cc2e411c99aa6ff3db6d524a21a594525ae9d35e29acb5ce09a5d2c426a921b64dd160db44548543a921b6ddf256712e0cc29130607742a96ade222ba2ea77b2c22199aa5faf4b6edcd3cd6ef7ee7d3afda350a99d5d9f8343bd72b148afdb0b375be6ec1c08218494e28bc6a967502aa59e412184f8aa15524a131f920fe46c9ba2c1e660912e7f3e8dfc402a67db6d421b45ba9a64739075c11f30e7c92f02e3ed033225ffde5d21abcb4f19d9f22fdf5eb8d9f046cc8361ee76ba9d6ea7dbe976206b3e86cdd0ea5a39c355d71d08216ba76bb1e20e8d3a2f76718752c8a27147731731253f731aedc9449171e7a584524a29e38eb438c5b3ab0868c7511fc346803715cc29249b53a594b065c6af4eafebcaf88c298bb27993aaf802671fef49dbc70f39ea7bc5e85b5e1fd2c0b02e59a064ece28e2e02314a8a8da33eedf07db8e92ffdf9f410d471cc759b50e03fbba1a0d8901b019621c5b3e7d34829e5cfa079efdea7d33f0a35c3cc4fc2b87f5954b7f0b52aa3f063a76b4f2894cea02934832694d9a272064da1d98202619841f36ba47c367cee1d99506c68c43cc8a24e4633fa59cd322ccbb2a8836559962bfd9c5fdbaebe32c3f7b5be1f2f1677e24edc615d754429a5238ee3860ea985a391ad17f6d88e138c4623ece23bdaeec53edfcf581e8d9e26eafc18ff1de59b3129da99bea3fc388ae28e5acca28e19fb1bb1eb6e5c7f3251042d156cd25a6b6ac2b4a66f32ddd776b6666b42796d4d26d34f2193b6d6647a6bf56b5a3399346bd27ee3f4af2841280ac10e6ea697461b77e27e6fd7ea8ced8432a1dc234a7c7a20e2d343101f1e703ad69e4cfe4b428ca9458283c3befe0ddd62183f84f4bea5f557bc70835db46bfb186b8fb1488445222c1261912822f98731d6f06b5624ca44af23c66e526dae1061ed491a765d2adab81060fc131ae978d3f0d3cfb8ced7566ddc8130d42defb8238b37076b5f4a3b31aeb83ec616bff71ebfc7f8a3ce93cfda9f3c18e3d8598c71ce183f97d9cbfe9530486f37aef43adb76d44fe2b76bfd26ad7d66f5efb77b97445bdbeea6f98ba88b34c2361db3ef94f22502ce908bcb4b595d7082703a1c563f407f3e3b5c996fdf9b41cf4e5baf0bb741f6773fa36fa854cd374631646a4600000000d316000028140a07c462491025519c16dd0114000f6e8c4c5e523c14ca834990a3400c42c630840c20040888888cccd046088626532e739645693f3d45ec3175f561dd32dec92c20b81a86fad1ad0bb4acda3585dd40b23e8ecdc49a88dd12fe656b8a2dbe2edbb2a4dedf200bb12c28840d1080e55d3e0c79ce045b1a5ee639c1a60690cb9f60d321ca4d146c7b48ab530aa2ede407a8751fded31e37daa2de87557e6187f1e64f3ac55a2602021fa0dc744fafa03d4daa7579a057f0602f4461c6a7ce09d33bbfe076c82ae314bf97cae95de17125892c2092badbc1ae410f45e34a7d7ef973503b470c8f759d5aecfc78ae1b743271adfdb79e8b6750d483009d5f433f02e6787503fff541756c562a71dc0f414e5afd9d520f7e0f2f3d80454243284935996a1fcda2509a671c1e9bca1813ab9327554b5186d0eaf60c89897688cc73ae8577307eb6ca755e4d481e01ad3ca66a90f11fb1b961c4b3ef3eef8b96dfa9a5036fbd5a7255eb17d988793ddc190fe56e4d68b7b10dd6a4ca1b06ec95557666199c770c0bf06741346e2daee31e1cc8a4ab083f1d3975d9fe2965f1638653e6087e2da6fb29365e22d50a7e9d561431dfbc0133942fe2e8b5ea26c6cacf994f3fc3472465e691d82eb3e9827c217ffaa36700cdd17ebfd09d011296a71d3fb7b9a714bd7f332b574f9cf3e272982ef7041a9688b7ec10bc075f25d166f97b624da8d9a04966d2b3c1264698433437d8954668a609e87d55f188ace997f8e9bc312524eb0d6b8a339d06bcb59caa0588e2915c7124dbaabff556c477bb6d61f259b0d9b4a20acc289b9025e1174e232ac05080c3adbd7e646cf2f3b7f3d9e6d9473e75f36effa8cefa34c7e0edbcefd57c530a477238b029596cfd33c699d164de815a7e0d7b8c68a08bbcf787ee5440ac0fd6cc8a68f43b9688b36060917d955599beae9ee1418df8405e58ca61bc9df2d703a5a8e0b5c3861cf9f7ef3868d3f5f7cb3f98aab4f2bcdcb3895f47536e526f119425f06861b34e82de93f382fbd93bec8d196a3e1cacf2c96694cf0bcb5d036ce15a039ddf0b425423b57808af361abd79c7827d8fbaea2abf3b622b21c41fbb8117c40d23a8778b01f70b2268312de1075fc1228203600fe119160134b9899b48f3cd567de088eb8b6e6b435fe12b6ad615352d8d8744d0d1543eff3f4ee8fb3bfb13080f9963febd54ddc2e2ff98843a67c4372591b84b8ce701f013cb80e49cbc0f8288937df241f30de9d2598b4d83901f23904217751c36b27b8d23704f61fc8ca4516dc85f00798de995256246f4b8a79a0e075e24eb34d6818ee737efd65e5df3e2fb003f6320d8518928f322e889024b92bd00251c9c961cefcc90df4574a8212cde1841d3b14932f1d6a40807fc05609476219b2ad06791926b5a66057dca5d43a2d1852ebf4df0c0a5e6f182d206749d13e55ea516a60b9db6240f71a599c8a143f485f174e1b830a7284c6d349ed2a308ccf9bdbc6974a14f53c1094bb66d375dc4e919cca9e75046173a0e2519bbed66e6feab3b696fb69cc39953047586ab950c77eaea05cdab86f54606b468bf29a834441231ed51248ab291b4cd054a3f4850bb96a6462aef61157f4efa41938eb481a118725a659fa82b8eb78934dc76045b131be78c17dfd1fb4287a0217d7ff26f8cad62f0fb02f88940e7417f24006b898864a4ec7fb41aa32e8bac42bcd4a9f343d1027effe85bba2765fc9e6e0b1c283466dddf513a2ef297d6e3750cda8a7aa4787818dbebf8fcb40706be643f63dfc855b422f0999f78e033b77bc1e740eaf9e0f3b57d4f46b32a463137c8ed591b3b20c084b6026aa9ed6ead03e9fd5484bdd84d82219c00a93de454bd968e8cdb0af21886a2a7cb9dcf3a581d7412bc16a38e31e4e59787e223564b20e557533afe2a89925b391d3c53ea9bf8d55c953e80760f15f6a4959c44b6f983d16e38f695d52282c6b9ca97b1188706f8a5964451b25829148b6ff47c35379fd7d0d581ae41b59b8ff23f7586fd18f7319dc63af64cac7cbecef3edf3f2f414e0ffa32d0ff5b58956da5841be467448c4f4c3b8e1e1e2b02ed9bd0670b0095fdf59978640e2eab9062cce5149f06bb5cbf56b1014d3809889807ad73faa83d73d3682af4ef5e4f781a78a87f751078c9dc6450c6cd04f96f691787ff0338950ef5d8680de36766022002763af2377653da1a8c3fc6d7a040028e8a67f5b3d0bd1f459d8cb34379b68ff979a7e6d21c93e982c24cf9a14d374979cf39d8235d9fcf22cc761c177c745b89fca412b127efd2d9e0806e732809bd1c73026f8dc4b7d20ed46f7ee049a46bad20c32d141a8c7e644124e2d3080cd5abeda3d530e5a635b9818362d9d324097c42b91731a3673981584a29aff601d08b4bbe8c224ecca23422fe0c3016512655ecb94d6e0a8953fb8ff0749f82ac37534971cb062fdc0ae32096663d767ee387eff125a6579d3275d07009fd993b785358926cc190f5c7574bab36195f84e7ea6b18421b8889822ed40ed7253d7eda1ae049b5b47f37acaa2db4f907e4a35ef886c7b043504a56195ed97905cb1253f96214ea8524a6b194ff49fb1380dbe3876406c203f2044a9ec2c9f3b3b007a69c7776aca285abb3cd53ee50888c0f92121f4733228a4e46c885be4095f8cec6dfd1ac023d848faf43d5cf6868447cf3ee147abdd3e023e947db49c6164f3862e471fe75c20a6cf79c94721b4482f683b294611445d009bde5e188cf341c146a933bc67d240ed3c5b9bf5bf8df23cab6cb9a66f0131fa4056875c246b18663576107d55c53fc463a8907d6d86bc9fd31d5e97d7f5ee56c85f86a610b89a50e1d871d2e42294eda86bf64d50fc5915060747b0b4642d61a44ae30d2897a657f7447c901e264768baa6d975ff7b4fd1c1f007921019255cada98968263c03d4e2ea3561da1f8906e735be0e4cc7dc08a5818fc5ee2474d0119964c3d20b4efe8338b1635ba375f9e64c856461e19cd438adadde6cde3e6fb1cda5cf969a019a63376811a5f69b20055c7a49b1a7e4fc836666bdbde2481a53f6689f1179a4b4aff6d8387bc4b3c40a85c8ba766632c087351b587d3deff28c86c2b1d0673cfd676392007b2bee97d60a5bd7752f76f3bd2f9e407686e4c7bb7abc7ca41aee76683a557581e2620536cb775cee25753bbf8b2845c7540f91c3e37e61bb700ec21184780db4268a10718cb3f414910899f4fa7889a4365089d8fc0073b5c11f86fbade6a166d636d1978e80dc19088f83900d3ed4c38e62d3a40ba531f8d3e2ee23cc807491fd16de23701806b5266fbea77110845e179c05570a87d86fe8d04b93f419fbfecb6ff1831f1296822aec3977d83ff3ddc148726be67dd93334f5c49966b52aca66d6b9b04abb80369d4f07ccc83b3bf60c51866f564608999d7cdf728c11d44dde3564d7211d002f52528864c6c0eaffa88d3106128b1b743045318531dd178a7dbf017790699366424c5e99d5000cf9c4c81b927bd047760a4b5ab4a86e55f7ecee3d0b41d8e155676a36f60e6e0544e6c38669dcd70e85774dbcd8b5c60d9455c6926a1018f4ac560d70bf606ce40e925a5ef8a8ac8805c01784b839b38f85e01482204a9854e00e39622c7c14cdf5033ab5f190868e7c437f2b688c9b73e1a3850b1969349501e36ad425433ebc45fd19593b9829b688b235bc70241717f9a94d2bae89efc7d40788ff54308e4b77b8f7ca4aa052e1a3d9680dbd7bc247ada49080ac375746a57fcd859fc1760c1df16df34096b080cf447172178a2d056a64cc65d94776ad909bc15067a0960500087dc4278ade938a146b577af9c231d47785bf147f7f6e01e5e82643dcd388dea1ab49ee33f87642f8280f867e2e7b835ab8e3d8e6d2631f1bf34b558d8188259116edd9b01e7c9483f781fdc6dca4f74bc3e64aea0c60f0d1718a16739bd88fa2e9961a1db1577689a08f524c4b3a4580695f85f7267654ee61eacc89deae96a57c6e32301f3be7896070a523c82ea29a22304c7119af973a881e2202e5aed078c53606e3f806500a39ebbd09ff6624968dc70f99ba4c29ea4e76dcd686a9915fcf554b0352aa7462ef2831a35e5428848229dae4d2e3c2a3dc7d40f6ed2bce24b12f7437242f63e2a70a4042514850c882b62872fe4d330b5823064d1e606d623c23be9c872e5c2a1a9af95952f191b31f8000b271c2896441bdb2cea41d111978d064d643b122aaab52a7dd90e68629db716441096cae562f98e8efd2e5328e5117af9896bce71232e0b22917e972e24ee7f03d5206bb02911a4b3ab83915b62f72990d8a9250a99531b56ea3a3d5f3a49df0becb8be6889c3a17695e175f63f3d346c36e5404a974097f6cf7520a1410b09d3cb4afd1f8e4e8609d1e1d458a54ec8b12db0eb77679c79428b5aa7cdf68d450701c20a4025a73de316fe58fbb6ddcd63aba2cffa23f7537306122c69afde008842e545412c6a29311de30088671fa65f3982a991772d8c7758010af2ab956477558e9a7369f81e81cf63b40baa4c815b66db78f30824e32db5c2ffe4dc18a24bfe797d0f26b1d9b4d11d85d3bda46e9b1eb1c88fe21fe173a94d566114a0d2b99eaa8736051c44b042f88fc093707a3e99873b81ae1e9ec2c0d36c3c9e4233586812adbdf52d61d8e0625f5aad209f7dd367c0c72522c74568cd88e9e0897d785a82dfbcac1255a4ac0c760275be002650bc073ac407a74839cedfb8077c90143c491dbd38c261e213e27f51f233f48b4a7a9d657e4e9ab40a7171e7875d1355718610b0ba82e23f44fa657a1cd5f704f1ad1a318a58efeae3114b270d7e4b52cefbfacb187ef9479885e01d053defbb254827a9cdfd29640143511bb170c7d1b3c3a560b3d63e302fced3e932ce4ec94617ec234520053ab8b0c259c75fa4a81141117ee1b73a699d68aa597ad42c27fb6805d9ee1226e6a59c47b568dadc877164aac4bc347389bff7d2178fd9ccd6e51e32309eafc7e840f5ab571f5fb095fea491367bbc8cc4c99862c98d78b580b3e9de9abfe9d6f7818de0757cd70711d45268ce1f1b3727837e8aa74d9bef4a777261aea44d685664bc74c11717a9085d5159632d0eca70b293bd2ceb3afb32b95bc64b0790e73bf38fa46291a55075f9f8044e4d5f4b915776393a184101fe8397b65715f7b40fec480cf94d3c899d091fab8dff8d5649033dd635d23d57f39c035c028b14b10ece2fc7541d5fd7d5dc3c6fa4577519cbd71412fc2802e6df1f75182a2ff70497972c28da851cdb12b1827b047a7cdc066acb179136f55f96d6def4bbc1474036dee6933c579491acfff87c64b1fbf5a5d4dc4775a1393de8214b969999411c265b4c25ef36c0408b3c3409f47f4ac94b3d0bdc067b7265eff4d9a6a22082bae04b93c9f496199b8848b4922c02523e6ab71b431eee27992882167cfd7b75a38f18f916887be28eb61a48406d6df8950d18374e0029cde8ec3cfc0285239c8cbf2f792889cf503230a0dcbb3161c56ce99e37445c8880a6bfe1dbdf7dda660b010aba5f14b13d1225a51056b3b809fae885b570c9870f152a44545b0957cab7d3716a99bb62fbc9435f7137cc0b3b842cd6f3c65d46c7811d323679d06cf24756f9c74eaaea078d65ca0b9f78bdfeaa5e2e2506549f13155445cc2f7e18a728e74f641bb58234269767a78205ce81fde2677b2d368cb1a9166e8188638854279f53c47b2464483978d18dd25bd705771ae86e501b01e6f8b07bab5ced84f5b34786c33d65aa7ecec152dcd0fb43e10b48835c52bcc04602a8cd16f36cfd2c2ce6fc5b64212b1d946b1ba324666d8da3bd2392870eb3591227b92834f39dca497b2209015db0322b16fc1fd1a45c5bb591736c907b7b5e518aed13f3cfb1c5fba7a911294c3144fecc4348e5cc6b9a68f04b5dd682a0295581fd4a42baa079ea5b2a4233b5ebb4f4da98099fbc4878e03eafd08fe7c7f4cbdc2e742960dabe2fc979b7884eb47196df7cc0fef745a0d4097ad87879113393e48ecdd7357bd61d9f6318de47f1ebc95fa52a216ac08ae4904838bf99c9364b838ee45e27f4107bcfb9d5549fa653f66e9bad07824dd04ef0011f4331928ff467cd22fa427d0330c115ff85b794655b39e9717acf5a6508e65a240f54780e0d507b586c8260915b93411a9f1bd38d6925a95133ac20723f2f71160d63ad6a20f9af5ea835227a63a8c71b496a2e6cf1bd105ee08b6c5580a431ab4dd62788b699003e98296824eb17e8d4fd8233c75f4f53725bb7114df4c1da181afa04192a80406c2cb2921f58d1aa6a2004d080f48c2b63913fe02a1a10de875432a678f7b7b7ffc3351d02466621b89a9768c695de26cf1aa196f7ccbdb5b08e5b52ee01c9447cde52132317168fce2d07f2e1d7857857ecc7581be90139a89ef5e002016875a6f31ac523ce1b43b1d8cc5aed75e76fc98102a42d29e00a72521708db3415de275aa47ed08f17390c6157d0f672dbbf95524a046fdbd5941d6536ec3655c1cacd3f99a46ba5e87a8ef8f1709d52f42a8be6877eb7e9045a58e490d3cadd332a093f69139b0a15eaafd60f0c02fe59650fd48aaecb3042b20449a815fc2202d8197089e30a420d8460a03fc11a70ee6d294604b03c780c904d8a1e238d042e475c21720576780e66d077f81d23056ee3ee1bd409b26123249f0fc79480c87399cbc58f7fd85d81b30ae02503ce9059af503508e3201ba8ed3c9d215d170cc5390c63f9446927a42b6362e7c07b963082af51d3e3f3cc8ce5cc0066e4e22249af711328cec47594ab688d1f751a6301b4a3a96d7fc1bad5028571d564ff7a61b15865047ed592e1dcdc08845f389d707f1e1a9af5c4e8e2b75819e84452f7ae70867e8e1e95d783cdcf856094f94f33b7ba348a257201791191cda6debf75ebe323f3527fd915e98a1ce1a06c04a7c8dcf6f92cf23411afaf734485d1521961f966014078a9d6ebc2a2bda02d2c8026aec4b851a74b568705f34cbf6e800089d97fb6826cab5bdffb13497c2e2c00a54bb4acfa3a5ff863053e13b986e146558510e3ed9a7403a479d7714225b9ddcce013f4498e6a8502203200325cb675b8998292b1029d531749d3e756c4b0e9d892840c03f04448b09a664abda0312717621c79ee08d2fcf23e7db4b43564a556e47dca582618a08f79e086476ee732f0f7638f815aca920c2d6498192bf740a571af47666f2b61d021187db18d83af646a67b252e414f8c726af5b45d45601c82f5b99ccdd95da497ca5708bd7297956088d42beaafb117ad230870287a95b8befa83d2ba7d7b6105d231e320d4593791c0b59dfd0627dd2c3ea1e06c98111c2459e5d623de560bffcb96675e238a594abaa1f243e3506197f8688edae88a88cea9fccc836f2a01ee8ced0a159123f83ed2482f392188909c9bbb15e647a552f011f89c0e40372179632e74faa29ca752aaebeb52a146ea65e7ae33615d9a20286060a55f01deac18afafa69cfcde202caf4fef33480899c291d237a582b1e644f43a6a7b11f489427d5aab896a953f0baf1f51cc82dfbad589e33db08cf55c573b3074f6fc59e6ad7f323151d17d8c10340feac1a039ede15eecaacc4c882b886e0bc9c15bda395040c48c84765684422cdec1fbf78d67d74af569797178f5516198d11d1e66699daec98e8bb3b06bc5d08f90b4442c099b3c2323297c88df7116bc5638707cdc50fa650bdd69b902c9bee77a1720cbf9dd9e74c1c50c88c6d68460497dcfed9506efe358fbb5c64ea792dba1146e7d1891708b6d1857ae47c2af7e6aaf913bb1be00d96f278c221de4196a9b6b4f87a6ec7b85610170340e68e0490cad157fc596e08ea275e613cf680cfc67c9c2f112e7d9c5ca811fb2b2555c565628bfd75548efb65c2fb82e3839891b74d1f2915ba06d9ce98eaa0ecb45157993948623745a6d50ed6b6237071efe1fee5eb557592b2993c01a85433e733ec05f999e4c67bcbb9a628cee91b3fdc48bc21e570f8040ec4cafe3de2a1096a9731cef2d59dbd18820bffcfc11bfd03a5ec0f54bea3103c3aa9d1245933cb5af60be8fd2ad706d66362a75b875d993f16b959bc009f4ff1ca5ba31f65e423177b94b5ae0e6b31197da43f8ca7ebd1b5d32ed878211ccfb7aac512222815b34dd753403a3cd673bfad2f8ed53c19e6772e7599606f2dce25a19554a3bf624369fa7dcf4984642836dbdf2912cc81e4701e99307e3f1237d05600e369a4708e0c10c6d969fff6c204cbf9e24154f15403ed6d23bdb18094722f8038a9a06234d2c6dc7c0660757077f53648d49874cce1b6f7249a5ba4ee85d9459e5e89089183c8cb9037bee453d42b44f16aca30c0375f1e14d87bc389e552d2b18c19cbe7e0bba58b8850093e602ac3fcfb9e3a9e74b24c8aaeb6a0ed5ba974c2e73a302f8c54c519c46e81123b1f796206681fdd0ebfba53b41a71040adc31face03a7c747dc3f5596c1ee1b2762ab92d89de6fab402b081eae9b0e2d9fc7e2556d8a688850ca56b43e82deb49170751447e53c494985084229c10803b8f8e0dcf71a4d0c7f69b1de251be75ef6f4eb87d2f5999442880dae6bcb89c2978ee62b77eda21315a6ed043357c7855c01641eab6482baca5dad0a5b1a846998362945ea803e211dc29304d3eea73a405d0a4d280690ae0b5139145d985eb778d4e3026f084c3e52e8b30d617b56f202c0f10b906c143b15bc21822a8a17cb0adf97a7ea642e06bc82a8f28958d1eccea55242a8594744e001a7f5e5993a7365974a09fe1d45547014192c2074fb6961dec4c85ee248ca194036494bfc8115ef35068ed22d1c044124bd318063dd4d02b0228af7972c9fd84cb7f177cb3b39376de1e471b604bcd49a4b47d22e2359044ee893631128de6d55caed94ea12bd356d692d3ad9f079ec75ea904d62834f2297b3463c142cadfb23590bcc5c88710d651f942ac2d2bab2b9b9ea00cd140d96ab07b3c42db50ab5eed460fa1ba42de5cd7644e6310db34f8f5689ff604a103984881cf0bb0ecac86e42c590b62503fc997b25f29c82fdea08551719b9637d056bbf0c7c9d976195d6ac04b5663d1419a8b5255c9973bef4cb5c6b4f5aeb94d1bd3ad9c49e545bbfab2361b50ea441185bd2b7801413b7950fdf59d7f1f151e98f081d8a19fe0b8a381e432cb485a3f550e10363ada10b48a892ecf00f8d7c1307cd722542e3845a17b24de917086390bcef1aadf7b8d3a755e662d01ad08d95188fd7cb652029e80578bb1c7167bd1aa701c0992b2ad9d92d37acd0da8c0717a94e1be5f43285a228629318f064fd4bd93798ea465b2d397151a1b5f93cae42e36679d8355e82da3e96c3e1503655a6bfa624c6fc93dd91e879b814aeb9c13c61fbe0099fc2e49ce86e8ffc4c12144cf713fc93604e6232df3d0e906282124914280a7a3761d5f8e30d0268a2647fd3cd1384661781f6c6772120c42556a18588f0ca539085466d17b98ee36591e47e48e559d1be7b11a456d65b131aac246c611688c23797dfd00872bd8bef382c5214404ea867870909a59caf8b7642b2f32b0b093899b4177650779ceba5e04fd66f52c976fc4608a6e92b947a9e45603364fac4bdebaf274bb0b96288600f93bd9b0af10a8f9c371b6d59378bb6351f0d127663d0c8d9ab08cd0c5eae7fbe2b3e6b7668b55190bc289664c0349254bd891f58385a745431bb96dad01577fd929dcb7636e318602d5ff1152a4149a2087d5b179033d9366a7af3c3eb3fd718ec9ca276fcf0c1158d7cdf8e2ece482f78465aba5a04b55d49de5d68cf8f54d38e615efe9909c1126dc1cba1a584e2b334d924a9ff8c71043584a0b29fcf8fc809cecfcd48d75b4747634ed3bd73a29a4daeb1027d5d3be85613cae2635f9a15f64f18d7eee91fecd335c0ca4b624252a9323654b6d721df8cd02abb7f9e48c1311f63aa329484ba001bfdbe177e0ec15f6e2f4e8fe28ba8de88836eef5914b22882069a7490f96220e3819a09586beb41f87c3913b5ed9296eed7da14ddec110d9b9a04dc95bd471e4938650918611a7b914a980641950ff31eb9cc17aef7998dbed609212b032e2de302bae5187a7e9156f248a6ccd43b127f115922138b06f0beb1165f7ba0251cb858ca0974245912cb0527e70ccce96526ce82ee04e01115ce3772cc85f93499288d91cad6bd97be49d9ef88be4a0debe777e43aef293460b60ec746200e98ca0b2fa06c2152f9e83797417a4a0598fbbb040ccefe8136494d8d8f6fa25eb7dc50cd59961b615153eed0316f34ea29f1ae1829e0b4a976236a979e6123d3b39f757852c89ee2d35c618bf2e6dc21b77caee950c74811836e1305e846c652eaca3c21c4755d0f4fe41b51695da1314ec6b36e5e22f8300b7b6d2c3167771c5ac2793b3e9632094068e03a9018fa2b886532ccba3ce6ffb4232b7646a2be99dd567d394766bea4d69cb5fcbe4d4d4d63db5380e9f4626537213cb91a79181e3e1772ef8063879578d563a037c3bbfaa872223147a0bf9c909c8d676f10df19b17dd7c6b4d7f93982c193199d91c0b10740d2658a0743817a37fa17cdeed91d54ee14d8259cdcd2242e3c6dfda3f78e0b3364cced4dff5b292338fd13190b3edf9a4ee97932e96df046be9491dd4819d93fcac816d965208932a26a28233b6b06ef74ed808ceab19b2bbaaae3dfde7743980c3139a449319343a3c908ee9003db52f01e792b88e86cc7f1720b0108c8c8f3967eb4bc07f4049561604597929523aa2494064a045f731d12680d7091c80281bbc2fbaf1bd5004bbd3e04446f65bca7c63e98dd7d84b51f99b4b759feb4b74684bd65d402eda93234b76136050787b8eb21f64e69a4b1b89e5e881d6d839fa5de2a09a0f53f6a05f559c8bab92d643420fa6df91e19abc731cbe4a3a71d295c00292ac8e7ec397eef4c820d0ff8c1da13470d8fc9a21732a4e5d80540daef06732473dc6b0987e5427f577790eb3d0ac955d999a36b92b0e5cbccc6f87288f59319b03797f0bcf048cf69eb859530685a2b978aff9294a1e51099fe2ed0bcfd786f42a5a311c4088543082150609b4a563b062d356c6546a75e19712ecd394c94e6dc2e68b1e6b33a87be940763d76e7fd892234c1b19def524f48dc14e4e472f57963b2d32ffdaad9b7c660a33a044c4218cdce81c5bef55df19d629f8ade648383df5a19b247f038f4f4813b53cd0feb13a894d945fc23c0665848e8f9dad5adaaad3a9055148d1d7c08efd817037bbde59507346a0ed1a0b138c599583a01ed0d75c8dcb3cefca8e829df1f50cf4831ee0c965b3da1a19705148b94ff23af81095e2c43784aa9c4f731af72f4d6b2c2ba5c163cfa69d512b82d37e2c7de7635bc8cca688a00bab7d34f0e56afbe856b54afaf6e9c48826f7414f09e7524ef7b90f02a3e3e8e587843e1d2067af0a46b3f4fc1a939a95799971267e67d0efdf43871e90de3e69e306d43a9d7c00a19ca9d75c233d36a2f00321bd1ff9573363e638072312e45744a2d4290257c46a8f06f8ce903e106e9cafe86e7abbc9e61d5be734903dbee8eea82b65f1a9882965e8010884c886107104f92267a6842ae2b05480b48335a4de755e0438ebc28e21c3cfaf5a0ded840c8cb66640d5a051dfc9829bea565ce0116539f1248ea510baaa10fc8624c3baf093f8a72b201cdf52108bf3ee754beca54888d9dc113c4cf51fc47f7f3345c57d3d33e22da8764ba16a705095f460f7652881a65ba7e68c3ef0d07f3c4059cbcc376e7560412b5311f9f3bc387bf54e85329739df9858d818b1345e960e59004c2ce0885927e49695b316d497af4817b8e071c623e569baf04cff59cd766db2630d3c13550a16bd45f16894b5d4fae46e6151a580c2289bf356859d3a468db05dfd0a38849af410a717989f191239f1e4bc3299b91b605d39e1c5f9d7bace3650d0fc75069caf412bc744ac3be03d5f0cd8b977e10751ff5495de0549ef8daa731c37579699f6b7220eeb7b50492e29b80cd141c1301481bc1498f04c32c0800ee6e660dec452e70cce6ea5c26b1568cce4abfcc9581dd2306e4b1e002d5235cb12efc32d834775ae7f97a095867594f5fe2fe62f18aaa77e87c8d75f74b3ced29d05cb8f9cf82ae1d0df521c001d42832b7dce4f7c8a5aa032d4b1e0c0e37ad8931da0d627145e44cfb12629661ca1df83ab87a07e973da801ebe4cc1b49df5089a2130cf7890b79fe88a684fcfcd83062b2e426e5211c52fe21fdba135c389e4e1b74c97540ad8ddc599d62e6f3c455983b76a51328bd656044232259f70b8d034ec89e49185e07bb39c500ca495e52f117bb9e227a8d74dcba6bf56d57746bf90fe2521375e8a0d86d7e108f77b533c60b6637ee529cac1c798b638398cc397ee34b641e3388da3b5bee528cc3ed443d8ed15224394717466fccc8651fd1c35f2041ee9e397094400bf1853b2aaa5cc223d96d945597376fc641037a6856b112f0328f83b283412c053bfb95132893606bf36a72f6f663ec3e2de4f13dc78286f94f02f053ef00a0eb9d4c0f35a07309c2388f970aecac146194ef31ca07606db100deef373b742511c7bc33c958504d8f5122788826c7087a98457f9b16d84c03e80274ed513f51d4069bad380ee84cbaf19cc70f426402825ab0c7eabf5a558393ff7b753baa29e4b2092a9be1d2d705acc54427e8acd9e83b48c8ab92bb6a7a20584a3d3294767fce9e0b3c10cd9b94c35e3181fb80f27423efc41d1318a58a667d446895724ba5dddd1e743d0d5586cabf7e3809619993c93acd96054379451504b916f08fb8b98f0cb03a8f358920899e7a0e6a10ebade903d53989fce966f4c75d4443c271516f6a8f5ad7b5ca7faf6d0c2d6089339c5c3551df0e91d3e0cc34527ab356d29af8ee8d2cdcc6d245890d65aa63fb0474ec9c98b214f1715ef20839df7d20d74c6625fd705b705ed96009c5a208fcc15bae122b9aa6d493a73fb6220b431ab27332901b5c35687d95a2874f96c642daa3cfca498a169d8e3483e64c8d742cb460d4c024cbbcc1afa8f6d0df4cd58a8316d78419aa9306693b81145a4ccb726b5dee1e01450612320379b03c3f08b02bfaafacf976d06adc937350806e317228f3e5b3d1352643c46751900686199cbff6e7ffae8200a54c2384cf41c691c699156c99d704d1949fa6f343e34e0b0085ac7b136ff6c0ecbab65454f29c2b3d648c6461fb44bdda28127815d69271568920daa48b2543647cb95e3029bee8144f9a3ae2927179a94963610dad0c19e75ed8302019d28f1cb2eac21cf6685937239f6a4ac76d89f378ad5a4b7d90ae6b5e54815f0cb6c2aac210606603cb4ab13013497444cb69238bfd9a018dcf72973bb0075e2e37b67829f930f9cde0b588011088cf892b4fefb17bbff82783eebd12d362f7148bb001cd182780c0307fc9a081756f3420dcee247d461fbf05f292fc197dfea93e2d9cbfdab9dbc1b3d72274e581898b0face09f103a8965f46ee88e8eb8a2b86aed3cc66882e59d23861b211643c3395ac263409cb792b8b0ecb43014798acb40240e4c18a6a768ff31b14b340d88962c47640ad35758de28f6c2538ec74b80bd8ba9fc74ea61f69553b425bd7ae7d6d919cabcc7332419684946e61a5bb1bc8fc9aa98dd65ea08df055de0d8d8f43f2d1e0ca08fe32c919a7471c9c3718b50e717806a62071f158a23b26b145782baf44b9a6d824e988f9d6065426dea5103cde1ed265424f573871a9a5f49f6c24452cb186b22e2147b1c5b1032887426e08f2a6c196a41c02a02e3cfeea055eed6d6324513f822353e1580c877553046a058fd5c1c629e6e96aeff9496f4e564b8e1d0824f74bd30d7840d1c7ef3d104a3b38eb584d8c91228a9d060b6dd90b2487f38a5dabc71c8f4431f5ea187ab61b29a87188e1fb4a6c703aa5945e3c2f1ccf3da6d41a611541b4b6f804bbf08e21283cf186311b62f86a14a572fc6b2e48b1b8568c4a9a69dc4b4445b900b61a854cd203a11e26472319e03b3d7f48f6151e2cc733505d90a33a5e698f5429d9c28e54fa463ab5257ac3a4e913b7e48c26a210bf18d73b1984ca44e289af872c5f40129d6e446a29b0c967a0a333a3c49c45b9143bb484e63659047d4751c32b612826c8b1de3c194b124ee4b74253724bae4b0dcf359cfe149d21f6619afec7d6b9010512f2f7e0e2d167321543851ec923b817e32598c664d09fb453590e2e849266da5a38c65793a4c244bf14166ca62c62742ac60b238d3d9cb2989a7d7048fbe37d7565eae023c23a1a7d18817c6c8beb020abf1fdff893ef891ef074d343b93533481a8182357de8741aa312e6dc942d0c77fd271a61d4acf7a4d2bb563a45f719c1d0bec0481fe346975051c13396d6fd787680ebd2647b0891e0823a66e07e3af3c92cbc2c842a7daa84a57e31aff28902bc58b23de5a4dbd4164ac441ae800f8e2b527c9a7a500b32c5cccd689c7585196a6dcdfbdd3c2b4fc5201dd498c05c7d8b7b20301cd07905a8281bb595db09e37d152d37587e619c2096ec1b7156de9f6f6afd3680b68d7e879e1c70155f7ecf78e6563c06c0f86ffc619de6406009dc307a306336e66738887af3ab0e28ead1a612ecb5279af9f402af6d6ba1621de764760fd6957a69b8b3f47c273770d477de1540611b8ccd64168171c0371abe2d96fafec79338b1ef5134805fb5b72b83ffbda9d339208b5a9cabe5e64b1bdbd6cb5b27a5b63ea0efd10954f0137b3398cc67b2da488abd11acc980be4c5a9e115cc114b1c75e3bb5ff4399a42ed0c7b9b118466657b233bb0fd0ee9c9844e6189a3f468a023179d9fb91812221c5e934f84ac6c08cbae4ea19a5af7d1690f97775769eddaf105b33892e92ba407397f8ff12f93d6aca08b8278fd06ade1d803455c30069bee2bfe78ef3009ca9900b4c0ca25170b367dd797c3656e143fda7105eed9ff1552ed8a753118fd8d4bf07c94111b4e8c83d77cd60925f10498de31650d899f2dcef45b5355c72e864fcb8e8b032f2596498db71632759fddbe7ddf65780d3cf67671ea5d3463e9f0e6dc3321d12fc83ef71be043c3e3cf3308946e889f84c3cab7bccb01fd0a99c2a3a176ccd1c33f93696933d55a5b5ae9fb4d03ea5660019e748e747eec046fd416c85d8a3011ceb1d589bfc1eb446168b9fb524b6c9f9183cf3bfe837abdd787295612fab6584d2beb97ed4cff2e1f39325ece4c11ea7499d7a7eb15aa1c40804853446918b32b58f10d7cd29cb0818304eb4800eb6696b69ae2572be199f16d8a4c72fb3e32ce52e874729bb6434de1d6b862073fadd74b09fa38f4392ae703b5503b069764d925f3aa4bebed48f4b5f9db30aae48cc850d52bcc6e975652ae8e8a47747e7e61a8f4af6ebf77f44f10b69a9b2cd1b2a91156661ee8df53e036fc241eecddd9ec8c69746ed6a33690b77f1e9f34150df7b7c08dffc6b7874bfa007014d2d3aa6d3dc20692ad20b2f61f16b9428f49ebede116774f64883f269c6a8c614876156c877e0dc67c4e03d958165ac6de439a886a62930ae80db9063f1f01e79ba30df92b7d887f42adfd15cd39597488bedeff78ad309e30d609caf50af3274250f200b764514b627135e2d18337c082df38578506355ae113f17594b12637974fd6575a5ae52d756de1263c852412ef6cee6ac054b2b63d8e34b9b978b036a5d56a1d4ea8f72d6d19708145ab82771b6656deaba3579ca4e0a36131fa67b84d814ac435b104131de643964e30ed45f870b2cf68bd3b55a3c65df3d31fcdcf919d5a8f7e92f65d900c39d67b64b114ed80cc464c1130b109c874441ebf390df58f11c9cff82ab1d4d560fb792d1b6e3cdf53debb5ad9fce39c0f62522668472a0d5bf683b457fcf1762486e2a3d48050c51f8d5561286a5624c9b0bd3213862d073b4dae0ce5f4d592af1e63a6f5c0a562e9755529fd1e15af008d3647acbadc295c3e9650c2ea3786b59eec94505c8fbe426de566d435d6c085f00b50babf58623b1ad3fdd1086c2d7093fbe9dcf40199c23cbbbb12b0b94c64c7b60a97e40c302aa427959a1c2afdfd0f100c05f8180de9d06fe808d861214b8759e19d48a914ed482ae11fa5844bd2c08b34fb6b30e9d3adb6a00bf0efb5b6ff8ac29ca5c426694e7d4d717afb9b11e5eb86f622555133a993ae56c93683be914d728ac46badbd8cfa896dd7ff347bd659f6c7a166aaa264b486acb248cede20c9a1e7c16588f9b0831783ca17f0656493ef03613e0dd6315e202cea5be726b1398c284c3d9a12110f78c0a72e640876f8891781ac35d106c30e8d80714c4c1657b99dd640c4033e107beb40b00fbfe24db3e481914b695357c183a2fae78c85b687fd6f6de8dd3e284e2b8308d37766c3a3e7b48376dc0da3adc09876b0e30862a1c0ba8692f359ed1ff366e8a8061624588bc079babc0665fb54ace8ee6babe374309adc4c13218d2e6eca37fc9ac69efb03c121ff779cfafe83b05dbff61e088e9378e836cf5d8be9d000cde194e8e2a38f617dff093b65eb62fc0f943581eef14d9a35bb794f5d0ce638aa85c4bb7e4e48c09ddc0a869b64251f8658b32e864edc7001dad3d5b34ec65b902842031e7aa8e811e1fe27f559bce3909762d8444e4a5f7bfe24b66a62ce07ed9ced1f93db5f78db1c0677063d71825bb18587247ebb996b583aa1f4bbf26fadd19d0bb307e7bb7239ebefbd628c09aa2eaefb7ac68223626f35e426dd889b3952b41f28c1daff50f6a123bd703483cba177b512fca26f57ce0c00efcacbc540c9c175943b24338f8164e4c866d472f05fe8bd8e48f8fd3eedc6371b7f12ca27e3bd698fc4935dc6bc6d94b1df0997f129b8fa7983b090bb5a80c2b84a52308a297edcd55ecda6ed9976f51e42ea29828adcfc83a9abe9344dbcc159efdc8ec83da1436ad0f2a6719d8356dd832c00e5c1ca941a9f3f8e3af7edcedb4cbab94a506ca224af4f2210ee97f47ebf430edd5965378d03daa7a79b9f262a7f1bf4b34d2482dbbdb50e20114e2590ab4b8b59e3aa551825b9c664fd8803a0b25c6aa5f74e42f0969780c9bba427cb67c92d17a817b5f68990d6fcae15871460a2c50968af40e503ef12d0a2d39543af2c6ddef81ca3eb6daefcbc85697726030a99181e94e4f30a04d03152af7d12bf9249288dc5150652c0926889299aedacb1451e390a9187fb9207f1359b9a40cbf35055bd008761124f15ba5c88a13dedc14063b03c004f3646bcdae0704db6822e1281fca624057d0b1bff4e688996482d1be8ddf6644dca643ead447a027c1a8abe0dc32fd682eab1849a052138fa33803ab1e3e1a5dcc1ade818c48933398747c9dafe2fadd8e5df69a40f4b1c8871e5a13cfb00e5cd15b44124cb2b7114071ebedb29ab39c28b5c0eda36e014222885d147990b0727507fb4f0ce27b4b449840da2196d63b48e80febb89205e88dcdce1bf91f3408336237330dacb8c3fea2341b6e3842c18ef5ff3256ca838a204494e284fe8c960d226920a27b7424405275c660671fbcc7aa6f8c388e812d39fd3b1f83d44a97daa9e0f48ef27bd6dc75cbd787fa2ad8db22f0ce84627944684d737cae47531a654333dfe6879bf1cd43e7ccffd622100d537e6bde30f41d627d77e52e237edf25353dd68897adbecd7723a2453b8524583cdc5c4474a0ed93daa0e68222808c11dfa258aeef6442d0f4dd78fec2202d6245c5124fbbd923d971ee6ce697eca9a7aec79877243f08a115555a393152d406719d6ffe0b718099a26c0e79e8583120be824cf48ad466e51fdca8a503625e8827c0057a0b423826e900c548062514c7e441a49cbc7778c6d4d8d8a29a1fbc8989d35073f2e8b984a3d41822796b208378ccdf58bcccdb1a700f28bd86bc9c196390e8075d1c288319074dcf9e070f3bed41935a032d9aa1e14dbbc1e25f37001ba1e37f05af8610855d28ccc6cafc2f8d5706f9f37e927786c2e6e56dc666b7b7a543e8bfbe0f6c7332e64a7ab2e9321f1191dcce22bfd798527758e45984389b63b4bb215b28e0d6bc21ceb46b7a8be0f0359afbe6dfdeaa00e192b919da44eadee1e187da209dddcbd5d9dfa0bfa71307a93c30229773b19a152aa9e67054d2c47c018a15697603940e9ebd63467bbf4134bae0ef7469a62d09782c4480a54179e3d99fa77d562f2ba1952d6ef8588a190ab0e44c1d044a720f3305372348f35bfc627a70780a12be49d52c1a582f922b21cbfc567b773dd718156a238e968c370614b06313908c1d736d72984f503c999c92ceb0e93a975de977c6e4aef5eb938516d538f4e93bd2f7006fbc6d008fdd397faaa9abe2c87cde26296e83c17501195233af77bb580cfd3efe12aab0e0960386e961fb99cb76546be11aaa68b842ad6ee28f2a05f7c559aab95662ae999a075831929086e088e8b0acad246a63fdfbe4c205d37dc993cbf9be9f20ce525e4f8638e721bf2d02d07d535e3d8addd9cb993b2c1fc979d56f56158da24aeab22f59c386fe536b58e3fc5d37f3f1e860d41d9c88e0c012ebfb2d08657785084a35aad7343305732ea6a3e736dcb112717c2580fe0207125896a7efb6a1a24b1d5b8c007505af8384c6850fb76ddf5590a8502ab8fefd4211232521e75f20a91c2e09125e0f35a39918c8bf127f1509282d20905467393d3422bc7fa8b97bc9519202bb1adfefbc86cbe3633d4f9257b4cf1c5fc927726c3ccc8f06e9e983a10951a329ea55a0ac989737ded7230470b07c2931f42dc235924b9e441066bff5d562b3400f9a5d94b30117ec87569037b128986b064711e72224409cf0bb87c76fa7beebcf3057ace118522c5de1665957f7bcb51cf64000a68a9ea3f242670ed8149b2585f35448b4acee0c4ac27a104ccd0a65d76443c2f1fab64f089cd1426c52c7c95999271af496bed4decab8b9293671fc108b7456769fd388df1e439560508c68e751c9e20f31bb8745bd234c2fbb57f8ebb562396836e54f848e0eec3e2c30df4d09ea29290a5871a47878bcf79aa46049ef9844254f2d9142fcaa665c4a0716cd6ef5e894aa5505639eef7c442d105968859e273456415678a91dae2d8982905d95e33aa20067aa5d7fe2708590d9dbd0618504ed02c618a8fc3f61a77fa496c4c8ce1f822f21fe06b8bbcf801dcedd340c3267b8d1b0f7ab93721f6870629fc5ed5a0c3629b48c5ce5e133bf2e2de7adb143ec4b296c5a2f2c98339ba8e29eb6b25a5ba23b1964941a372db6f800310b35f91355f28dc93fb3833fcfa40e394a8f33c9c69a29190a7cd13992412df6e40af78ee050689296b02c34ca21ee04499b9fe7e2de8d6accd0aceac3f8baa1e82340b16da21907f4cc9b629d9dc70bda2b09edfff8c59937f0775b6b5d2425c53efd0fd59e616a4940f0bc09d4e0e6f25fc0e432a13a7c1dba0fb1a1c2c6240103f6d23f8d6a0d6261e26a53e9aa6d08696fb0d0282e8f8ab4eea83396f7e444428f2180d863a7dda88838f8d1a76270ed0756ac6c0cfc9d27b36942937738658135b44ecc16e867ce572a8b8d1e7f9a12b6cb957c4cca8cc77f322aabf3005920f44ea72d40d5d8b904cb847e7ccca8424500954c7570971ab02bef8c698e8e2b06caacffd98fc01017a5360608ec8387d4dee18123b72292c00629c3972e0d6f242d118792dfcc981c0f2c64977c236800b948a83e34f65f429ccf13ee4579da761f17b1a653f33245e8cdf140c46d9442e3b565785998051e01a6e88e941731e66526cb0b25ae72abf8bcd3b7eafd1192a56cc857bf803190cf8cfcbd2972cc6916e2ba3a3985fe79a0a4e47bd25d4f9edf461382eafbdd25dec00b621e631a6272c9788d3b3bcffaedd516c9e0c8587a031356ab2e291705f4b4978c2ac2486333ec7703868becec1fbc31dffd2f3a4ce1302ec0fef624c675e604da9df3961ac3591ca2e553a8c09180efbbb53ae2c670eac7dd06361077f510617eccfa5feda64eff84158f446f171d70cf217e338c8e420351d8bbbdf54efa3855bb2834880e25e98b6be534dd726e462b4994d29d21755d7a753dd47c684c21643b74e6a73d09fe9fd87b5b587ae8dc39a0710fcf626718ea621b6e42d7eaef248c3601e1f5b298fc9cdc8b4d449ee3f3cac7a078f0de460f8ccdf222e4e97401d119795aaf13714929f4cdce3cff21e89cd652e10ddd813f0533f99d8169ab90dad4f7dd43c9e054fa8b3c250df7b7a22dde21d45802466b50d7cd15ed31179022721ae843218fa0725d66309b1e049de7f289e94142398214db6d48b84d245c535fac978d2819680790d7794d1bbbcf7c98a4d54d7de2f6278a60b5ac63313ce14032765f35cb263f069a8eb3dd5289b722157ad89b4f3245c4b0568b0061d29359f493ed6f754a399018f06118a57d609b86ce067dff15860c506c8a2e8bfc2a3cb33103ed4c30a37d941f8ec5050d52612ce86e33522661a9b998e284789f33535b541cd9018fdd33e3ac5da2d7805f83ac388b833c07a97fdc17850abcf2340c721faf3e19765abafefa586faab3985ae107bcf82e43785e293c159fcb7fa485498bc9a0541f5363a3358cfea017b6c7f8702fc46cbeed55638d75c0bdfc305a2240373e4f6f779e464fbbf625cf7ab3905ebc89c77ca2ce8466fdf6c5fe5d49cfcc7e4dd462eb51c1fd6b66265a660dfc610f899d25874c321aa027196c7ac5fcd193214fd143dfe13acde2a7d131b6ca1ab61c5dc7ba047d1b578cfe80e01a6912c0663e40e63dc88b160ed2cd2f67058233946aab2382b667618db01e40c6019f7deca76be51e94bb8ea6824bfa979b8c7c3c4d971d8f28d65cffa40b33648acabf33ad3acf1275dced5f643ee50fc4f68900eebb80eb9c2a116960c6d593af8d492c1744b33444d14be5c9d91413f0fd5d8cbeb701d148fc33fc97a0adb31cfce25b0e069a90d88e575c622445d73b587c85074e31e3078af669bad6999ce81042f767f6fec41cad6436ffc4407c1b5136a62b660b67bfbcc151233dc76f4bffd1a29d2e9a74654b3c01ae1d8a3870123c45908b1e0e83c67b7a62969b05f8d442bc45dd3a0e897091f8f907305d1ece25826709f3b8cc37158078345ea27a1f6ea34885184f280150921194963468803b1e6dff3d0caac48605e093fec160b674180cf085b3e0025ed5ef0e3e40c99a8ea31801e96e3d6d58224130ebc123e951f4bac9fb2ffc8e6aefbf503724f0fa52b2a322bda77d421e3b7068abbfefaf8d530db634c7bcf3f6546f54bc3d9ea341c688faf9dd6349b2a4aef5934e23e36b79cc974a1c42d544d711183dda1eaddd6b7a9dc26fc4b254136e1e72f0c2f50759535cd2ad0031316522cbb6cdd1cbfc0f53c40b25db2fc5335a7d4dd2f919599c497dc65af4aed56accddd0a78c0656012074d37dc692b2ab936edb7460a3becba6fc390b856fbc2ccb16042e5ac6432aed6d742834a2d851a494d7bb4e585db33a539c014ce55bd25b41fca4eba0308468b4b26000c2f2ba76e3e69cd2f311952c524afea669c793c5f11db925b40696364aee4d5e3a21a7757eb292b2fdac653aea8ea1c996ada9afcdacab6c97d85576324fa44f82dd49eb917768bdac12d087efb24321f4b02c7e46ee4b7cc60e4ff85e113774eb982de7fbc1ce40d07f6257cc2abd5363561259a8f08fa4786365c9c6c19bfda419fa510518c4cd866ec7892765c2701cbec65f474c8feced9c19d5bf4fc2e6fb2b5eac8cb4704613ae49dcbd2e8467f650fa094313289861e6e16cd1109c93b6f445e92c81a1bc63cf53128ca27f4d36f0c39d171800a472f9e29c2f90e0179c4c159e56ae229e7f23896bc9787304c39797e87fec20478601da575bf8af0f7bb0948a35960642dd9efa36f42e98097c3ea9120c8cea0fa8dbaae33799ce31a90a86613f6a30dbf0dbd517e9878d860d2d4eb314923081fa25d9c8c1227dfaab583f0a327bbe7a223049ca879c5730543f22851e3d797978f180d4d426c89cae0d6015843e21ee53ee6b2ad45e3a49aacfcac3b0625ddac4e8a7833faec8903122448487c48b244dbaa55aa8ac77124b463facdc8d43651a25dd7271cb08b3d0801b146c01fc46d5679d22dbdc70aaf5403e9bed3f4bfe3248f005589d6e45bb1fe2200752b04dc69535c04862b0ec965d15fa930db24e7b997a6849dc48d8c52d4047637b3a0417a128c9a2a0947dd6505b3d0ef09dd2da7362fd6320b09384aab771e2673f104528d04f7de7b2d9e68b63d184620d41d111f2c1d2df88410f317e2de4cd75fffb5d3f0c7a2abb2b781462460e0c8d8f108f04cb4729608c9646587449cc557379cfc595003472fbeb42f39cb7181afd5f5e6037a410f394f93a4566d262887a2d49333746235f4ff8012dcb6213a0592b352de66e832b2bf2ef2916b32746aea0fe2ea6a9ed8e9812ead0acc98a255bd2b92b3f2211a59841db0d8d5b42141fd96e6064c610da6729327ee53f75904bac1de6d5ac4f59ec6d804a6fe8c2da74c5900246f1e2dddcb07cddc8b33296811f43ad59a1a6f7a8feaf4c6857606950afb28097e709694101fa2bae2c1562fac3b6667c8bf4bcebf06e811f6107dad93b2651122f85e0a2e67dc7aab0216b22dad1c29f588426c568a775ef4a0b6f95b905067a5f810bb0a24809197704d14bc34198ae6b31fe5815551424f139b2c2b25db0c44c32c72e93ebfbb529bfb2eb879735db21786a20eae5484c7b48363047e2d95166f9972c46421ce0ca5284d4d0e8883ed89c7852d9769cd5b121c62ebb46b1b8c51dbd2b118767bf12f7b20f27d77f19ebefdaf6750994f1c411aba46cb3797092349a962691220dedf112dcdc1dcfafe3cc17afb485489651129fa6a33f8f0a9942869af4025e6b2ef638a1618c781fabab403619a2798e6e4465aa1aa8640cb30759fb194a96e48fec4edd9d91735cdf2ab6adc0410e7749c5624f97b7283347241c4b9e1f52628cbc541942c8f3a02a837d1e0e4a7fd5951a4b01c8892ee4ad9ae68d5321f3f0f8c80c6d03862888c43df8edbb0832b99e42431de0a92a7d5afad99249ff07921d5d44c1186c50cf97e10acfd48630bc6448f2044fdc57a02f33c7ddf1c4cc02810f983d149d17a27471efdda8b4645a6a54e88937cc0471d73c83730e02f264e5815d00426986302386439212fe37c1eb136bccba89eb596666cbd3b6c9599b9b2fdbc0b6232ef02abcd0f7dbb7ecb5c570aa3770d17396b412efd6d595b90e394e72958af7db572cd6f456b7b0ee8159359bea94bfa2ccec54f179cf353da35ea2236926a19c007eb9fed7a695d8d11d9c2ed917a345019a4e19c78742d2e73064893e3306d1adcb3f1da300cbb76402eaf17f5c63b1a39a9f51ad6d102d7c12d8d9a5da47b60e79908af0b850288736469f10a204b25e0f35bb0c78749a41359b88a8c65bddfd99876e5e2602c810acb9808d4282cb8427f14343bc8588973f6830b52b8dae1c4974fd914ec1f7ca9caaa309b4e03ec8c4e68bfa8e87498155559e1c1f56eead94f27f375ecb912042fb9d4acb7a30191801063506231ad7703eaf6242f2c2e6228173486d22f5c1304155f5461e6fcbc78950e6f63f4ed27561709bee260492bea521dea9ab0e047f1c0b692fd6947075003f2c82515a74c9bb937ce0d9133b7daada0b28cd82ecab155c0935ba6f4d93fd3239009fe632445e5d11cdf37e5db226c87bd0a58cc521e70e9007bf87222cedbf0c4d348bc9577bdf3c1800716aa5428639b0a676071ddf9d0b83564717e79f07946bba589a5048f8c25c12555b6dbbc26604168b905a68a049d4fe115428db9f2035afd2518f22901c5d1c021dc6f2e6182d7917d22307ea24dd5cce1de314903b6ab943c87c5009ef2b13c1fe08451ad6e235839827b20e7d32f8139cea17c7e20eeb5cab4df606eafe54a7a83a7388bb77841aa44e3af95d06331f5c503dbf07c592ffda235768cab126035623065ecb61569efe04235cc0cfce41c9dc702868eb1e0567cdee8ec583a9a5336d8ade4408eb7da2e0b35fbdf79d1146c737dc99ced3410a70bf4cd9c0b22d89db42e6dda319d54b5b39f6ed79f92c27394bf2feceb8868197f21dcf03528f20bbc18ae2370071c11b995e68e28cb296881d1271952222a942b8814051425c542c59272c909a2b57e8213a9c1a59903116726e9ec160814f20865cfaa182856ec5add8702ad1fb39d0230c250ecf9921103fc43afd59aa5cd52169832994bb4eb63ced7f1cfeba71f2d33301b59fd411a0e6f42866b8179e45a8948d6af323cc5c117480b707a3e3bca0e767873554dcbf6f69beb8582c9daa2adba874012fe32473d68a0325e6fee09589329f06d625566e765832d9b3883bd3400b20a0daa2843fdd2b680e9b4dd4ff30eaa1284a95c95b717b7f7b3eabb9c6c76b6b70e8e8ef59394de011f042ab9818f5d0e51da2b4f374bc45d827f79dc0a9715913c895114e4e79ab3b2b938b885e028c5865999adda14b14be1a73c8c4bfcb374877176d425b13d18fdf9c9573a739fe9b9263c364f2a5d6298b4a6f7833366403cd1882cc63be781e9d844efc9e781e6ae354c2a73482cbf0c28037bd3325d7e373ebc6cb6080d257cf7659da597f7b536131787485292662278560e00609f85fe2b202bd7b25185b1675ca5970934463265a6fd42a2235c41dc57ec6f66ae900162737bd599299d2e0b93eedd215b53fe7eabfda6a0b6ec6e0d6c32c4a686c547d87e750a42f05829b66db1ecc9a86e2f5aecbbba99210689edb3549ea5014871000fb6ac679d9f51ba883b0c42812a4935fc2e5d2c29fa52048524cadd0d3b3f45a151de2551e195c7356b179a2858391291ce9a13924eb119dadbc60d5084907ebacf7be0410671a8735fe8a995c57d96e8b616a199aedacd789ea1cedf3bebf39febb23608e37e14ea02aad9790d0bbee2f7fbf6c2c01d606570c5822159a305672138f4b4c163b256d98fba83135410810fc412ad95aadc9fd4cde58d316097b1e0ea92d26585b4bd157286429ea7a339d78ed688e575f31d14dcfa65c64f8afc46efcc2ece9e3378c40b56fac0f191c80c0aea909033c516236ebd6f178d9d349539af26305d734ab1081c93f83d8b65925b1e02c9190d659466dfcc1bc9f843ffcde46d39dbd424ad073ee404d6243e5a8c60d7f9221c401157ed18336239cdb9a3eecc6698e1df6bfe49c0972c00534fdebe32bd809ea00a058f5920485863ef16260daf45f63af01ff010bfc016346b3ad0717d1e1cd2161161b21bb8d53890672668dbfca03bb2972ed43387859fe06e66a9a7067c3b64ac8961ab2d9bb7d72677152848cb8d83a8309337474b6aadc8de58676afc37e20c9726cfd6d4621a1193eae82d6eea9b782787cbd323d3fad579529311ceb400013737eef17851c63e69d3c508f522d38d5ede79c50cd62172206014d4048573796cc3ddcaaf8643931be0a42518eb4472400d18872fb5980006c5691aa05209abaac0f81d25c96485f65cbaea3c2d64e2b3a9a79a7cef657dd58ad29c17590296d15e5ac4b5c6a5ead364688c7eb16ead48e90f4983f90b318f0f3b8972f91afc454f98838d4d33100637b20fd1b175d821e12c9473a80a3c6ecdea56d3bd62e6c7dcfaf72d387d30f1172f6808341131861a031b61d3faea438c867b5b02a98a1cee764b7e96fdaae10e18503a5aadaf066bcd18081996a6faf3c557437c34c6b6850d228e33917b966e8809440d0858e22e90134c8da8e10c1a530ba9c846619c066dc63f6e340c6796d1055cafaae00f88cda1f1a43db52860579bebddaf634cf77c96a6557ce4a30d87702b722919ad809f9adb47b54e841f18cc16badd8e298a67545fbe3acb4156647d4aa6d345e576d236988e4a5893b794d40643c527f055801864bd44041180ba044b9e90abe833077316dfe081650560e4ac1e0e35e57684b9c00229c478ecae732da8d49614b7f15a8c8552d025aeb45e2c3b9ce08ed6f3f8027bb8ac14d064d20fd10d6b82d3f5fc40bdac7d89a919a329f985519869f72ebf07cd401fd714b67dc1525a8088f558a7546ff549e7649f22c45ebf28eebbc0126ef372380d8d7fb02b51f50165bef3eb0d1a96383c807ed58c592f4ec71f5477d25d148f99bf54746476ce4437cbe8f37429f58a7718a442268f4c17c50c02af42c0a716da4db31ebe45540cb6b48a8300d58a414f999e05b2d2ecc39e46d0842048f3abd91ec057afb1677c2e36fbed3a9322035256ca195b273658eb36a1d8ede3cb3519da39eb13179cc1ca2911074c42bb21fdace13bae95775f0f62d78e08a8aa80ea58ba9ff5a2f4d6aab18f03829b761fd7cb4f71ee477d762a4d368b674b37757114c0ea03881bb0efa161aa2b736a1bdbd0dc5eba4610a1b7dc063478bf08aa562c65f6ff05de9dcf9a5b901d0ba47c8377cbe89c52d1904be2d91f069acf43c6c9823ba3e0ece612127ac5cc42fb56b72bd6e2d9562a3e4028e6bf35707443c1f9bdc6e970081104bc304349abfd2762e4918f59fa8646925e6c254239bddf2a9b2eed9d8f2b1fa7d3fbf5580f66c513c5b557d7bba07534026d479723e3032d60a97777cd918dc05a95102173bd1ae3e3fefa0db81eab02db14bbf62e2d6e993b61c345039e3c3a1c88bd73fd0712e8df51c7c015d4899c6f0da6520cdf003904769a8b65beb19c997824bcc67f1ea48f838400cb506cce5513a4adace6bc9c50aa4224b17df90a9b1ecf0ca558c6cc311b7378c6a8884c1203303b1c6ebf6691712fade8dd23be1d2ea5d61b656c26878382e37c99147ad858f9cf5023ac2f26f8b996f44108db8f9dbfa161f37bae2ac8dfc2fddaa9a85e8af6295d24794df7715882a58d392ce84fa07f0d4fbe865e0c61fc6f810dc389fb84d36b78caeff8cbef123402a45b5974408c1c48f75fb03bb36933f600ea69016546ad9b16784d4a1b6574e61582fc2add3afd520f81b3922fd3c7d0584a6cda3dc1aac1a0dabf915d45424a7bf4d0a66e74eafea7802065f4c75b4897f203a042467eae1c17fb3d36673e1009071b0aa098dd8677cc0b0df4913357679e34529b23a413a0033b5a51039a301db31f472f9f515cd5e3af74baf6e2b28cc50ac082f4a6fa0441544eb04e244cc9a96901bd44de00087edc804396c4c63c80213eb45dd0214f8ac1bf0eb728af0e13b5e21e4a6dafb2b650fdc0171d2bb9a55209655a975b7b8523e2ee9cf82ed0d774fd523696026c07e411a67415c8fd81a7e955f726d47f847018fa2c2bad287b87b2485d37d0621401eb1603ae3f3da0aff945c05eda3302b2dbe591cf14db01e5a82fb203f7a0e9e7f96258db36d3520adb007dce5696cbe03a37111e30643e11bdf316ee7e939bbe7b861436df39b4bf456ec351ba05ba061cd1fe546d4a6ccb7802b61d5078702240c2d63cb3f868f4ce883572130c8f8537787593146dadd7f1993bf0bc55a8eb0d6064f59d4e3996b4c98b8fdf7b16c6c234bb365e2e9b08ac5d4822146bf0580576ea2938385599302baa469094d0c8f708d1028bf92ee0abc698dc54cf6aa7b6a6851410e44e348abf0edae237359565e195f89425342bc6655fbec79a60d421a603dea021b1dd1b519a60f9102dfbaf68f63ab312d16b95c6a28a95e0300d7deeb3660eab2b8831e4d68e5651ad63ee55144cbddcc0780fb33ce3dc31de15c06ce97d60c01f54d3fa1c381f0d8a37aae6aa40cef3a82d3606ef96a3f338521196192c8e81bfeeaf08b96a1137cb93d462a4d96eff3bd49a8bd552e32085a35802e42592158e893eee4b02a80b55c99fdc75896a82effbdacf3d5184f383bb4eac9661a6940c06c66e4ba3f237d55ad707db8c3c612962e3aafb0a45291299ce6a29f539e5c30815de15ca668d9c0e0355ba19ad08a2fd2ff58a3971db4062c206e860039037d820867f433a2547e83cc94e2b406c63e3f8f7963c6ec2ced04b060777b3892ac3aceed635f1a05ddc4f8ffbff63a80bb13c5b08f879fb0cf4ab0241fd44b3dc6ee47e24a75e9da0297500c262040f3d61edaf5ccc92bc78d3b07ab4dbf4c4e662c4af6975920e6761b818fa4cfa42f0edc7c66c3cad433bf8080fdb9424e233255800f2d3e0ef99282bc6f0f0681c7c3a1cad5152a7bd552bfd5ab5012846a4b7be0ec79409f883b6741d8e562efa3f6c38cca2051e5f453612581050e9481d025f829c71c379904f0374e8a5b3e7d8e8a5f880fab3fb0188818c809e2649233aa3c84aed20b2b9116b84c8dd943829d34b430c7a5875c47ffada7d58fc47ecf4503ab4faeb3f7de87ff8975f5a237b4b99026212e813ca10557cb4af01560e398059b444512585b17e0ee02b8a2a298cdffb97f16b21562604ff57f53d88d57259b0d55aad1e746501bfb502bd857ae124493d4a29f572a2671ff572a2f77d0ae0ccd7196b5b2ef02d8d1168bfba66607d055bd685a5c995430a41dfcfb8b268a941dfe7904210f8f583ac3da95e4ddfd7562bcbeab57cd0ccab3e68e62bebd5f465998186ef675e4d5f961c3ea8f596e685966b061abe5fbd9abeb7ff217d46332e2d3568e6572e2d3568f52c57961c52089a792d3588f52ceb6a3dd86abd666065511d7950eb35431448acaf3fc3cc5bd7ca954314555218515f83ca18512f25277af69de8816f64bfd2e0ac00be7d2423f07f0b0bcc39b9401538dad6abea7fa7faca41a11e7425497972828dd68411b54f9526090a95d6cf654be00b07f5f55d202c85a3fa136c845958adcf04cb2d12d5bdda776badad469c58b0048c191d4c13628c49aa26c28caa5616e38f0ec6092846d6d704cbb3bed68e13b284a857d94fbd05df4fae6a539f7af04f453ed224b19ffa93cb698862b5c298d8efd3a1b6ecbbecf42714cec985737af0744a724a3d2b857a925a9dbed3fb09f52825d40b07f5a7878da717cc7b7c57b0bc47924aa5c2824955f9f8d110d593aa7542a1bea2be72e112a68af9522b93eae54454676aad33af4ac3e453a550a82d5b9abe4f7da94fa45cf8fed40497f1f4281b1a3636df8387b3fc0bcb5f49a92c51249dde2aa16a50a857b97caca1213aad4eab941d5b373d78882c41a29d4c59b620d1c58ee0b36cb46e589a56536ba5c2d13aa9deaadeae6898d426bc8cdfa7524f43a4fad43b4d68612922d40bb430f0ab6e4f33687e9084e51685aa19bf476174304b6c71022d6c4868bf2e6942893108e832fa27611995404011e387002de3f7eeb22510e74382f3bd68fcab0bf5ee6252c7ba3f158d1327a857d20bc4327e2fff643e26a1bef220dca093d37bce831b1c3f548ac649759284654459f0fbbe57cef8a4befdcf55dffe2909168b656d0af6d917cef7604e924ac9fec7258cd172f962f4d1eb6861e0db3fbd70c01c4f52557b0296db95f515eb6b75b9b538acbab27683e30aa55abd96ea08e230a94a2898fd7114a4ea7f8f42e1a050d6a65c38a9f7c749a1520f1bbfb7a94f29f5b131a16af5e0ab94503ff32b57959ad55757d2cc6bf524f5a814eac1127c317eff8919517fb262f4f1f445701945181d0c114c8c4eec30da8c0e260165c65dc7ef1db572e1d49ff955ea51a917d8fad4ea53a89ff915ead452a1c0577dac6f091c699c9c1ef5c2a9af8a4252ebf7a84fc251aa8fc2398138a7d59f50b13042140a85026988beafaf541f47f5b011f547ec56bd98c046d4a35eb0d5e9853a9d7050ef38a88a3aa16acd58732ccc554db0ece748c1c4e8a38f0610625cfd49a6faa4efe4c2a9abffea4ae5dae0f8adff6890b0686ac625704cfd0647f0fbbeef67beef7b9026098dbb2cfd5ca7f5c26102f37755a96392fdef6756bf7202ae5e382a96559d7ea53aa9be24587d7f7dffbd60df2bc7c25c044888199d95c3c20171c01713d8e8dfd32001ff737d39348c35954aa594525f55abb7384c604a49af5f384c6063fd5aa58e27d797f30d000c12568cafd1c120c16554e2a3fd2570ac52c954a91957b59e4017cee9c1d3c346d5af4e95ccf881a7c7f9fc05b3306b8f60e5e4c8d2faee3a58c6241cfb38f6c504063bc578103a4ec5a90fc24670b439aee33fc6adea95a41e7c2cab5712165b5f89bf50335f5f1febc9e9ddb25cf5224a3da8f4d520ac6a92a0360d8dad5247150ecdea67bebafc695c55aa93d5a75e4bf57b3141bda7725ad58a80f51d61c5c87a55925dc7fab1158449389ffd5e4c602b55129cd5cce32c81a37f959ad1bf55a5aa5e4befafef95a43eca7156afc259bd98c046d50b966361d559d572a00a17a0c862b4b55626d64b0d687d945a34d61fc072eba3f4d9131223f89f113b8ce07f556a46779d27a06be9546b036ce07501628cf6595c8cdf8305c862b4cf428139c016e3e9534a3d51386afb2c96d330a6583549a7df757ce1107dcffaefa5e42202ff54a566d4efaef33db1cf7ad7525daa23f8ac1ad52b61a57ef53855eae8bf048ef5abd48c95e609ea53afa5fae4f4f5b5f4bdf5718628f5dfa75e4ae0ef3aa25ea092fd5dc753953ada179880ea2e809faac205b00050802a5518dedcc0fc84c30436e68caaf1fb2376470c076195fa7d5f9da141824aa1689e9cc0d6077efd0fc6047cfb5aaae3097c150d123036d69712ea556f5d49a86702beea513444a857fd4753a52ed93f3dcab5549dd8ef34a6a64a1d3f1bb11d8449a7af9f048e3667b45f992cd5f17ba932c0f21e7e9a71e1d49ff11ec472d5d4b47ec66b8619df0adf8366603dcdb330f3e16b869a9f799ad70c5120d5fccccf60f32c960ba7be7ec6d79ff1ba12457dcdf8d60ba73ecd6309bad2fa9a17eb6b1e89f5355fbf66e6695e576a7ee6afd83cebb1046109729a1baea37a9cfa58825a2e9cfa4a915a1faa6ea078d08c0f5f57ae788825c8c675250a249baf795d8922c9899a67a1e22002609a826a6abee645c30c47c2b779f17cb1ef5bfff22fa60a5fa10da2fa195ff3493c4d335efec56a5e465fec041c68bef5342e2c412d1796a0afaea5cae473d5a6d6d3bcae5cd17db1ef692fa62ff67d0ac583705fec7b9ad627d5a616cdd7caa56ee1520417223be35bae9a67b140577d2dd52a76fcf2a548353fe375e5ca8c9ff1aa5ebed837a3e66bde83667c52f5327ef64157b53f569a1ae43aaa570a077c0fa23981b494552920081822880043440f6088c802668830c68c0e66882dc00c118211353a9821b000338405c00c11458ed1c10c31c4589f950233c40d2a3004588121c00cc6fa3dca005eed079e5061c010e08b14a383210013a31360887166743004d8618c6511fac7b2081d071361fd568c025aa5f29a4a558b42d9ef74fa4010044fdf7742598b8a3520ac63922a497d7729b156d867551a1c6ec2f103c790068a7bec7b1b57f29d6894b8c3beb77198fdf059a7cf56074fa1ebd40f6336b01a9ff4e3cdcf8d1f1c391efc1c8f23fc1b61f835c2a711be8d2b7c966dd5d711eb13fee9c3e7b1aa0f5da807623dd0f793a366c6d3dcc0f1ac1c385caa97fb84af2420d51fb1b5c6a54ac1136833569598a6fa05046b172eac576dd1be988d3980b6aa5c9a7a08932a171a9ad657bb24ddd4a6ca85e78a8b01551f589bec572e9687078887874b0f618ae6a7f59606f592c2b1e0e16952dd8025458918071e10e30058c1fffca65625c2ef95d4a3de845c621c08550fa85e58960d306764f5e031b2de59ad91f5e02bf680b08ea9daf4d99645a17846af64a480856f43a3d6af537c3f365e355a2a1a36420bc476a964a4e83126799830616ebe6781a8946a75aa2d1b3c926e926a52250323135b22b4b11dc224db65b45d6656eff67bd577fa1ef4626176e6e64faf141cbd4bb5493dac1734b64585ca35c57802bf9cd1bfd563eca13a559eaf4c095e6cc08030432b6d9c021026391836b4b058077800460821c008a165548d0e4688262264c004e102304184b16082b0e20313040f37dfcfa74ea9d47bfd4755952a0c6f6ec21b87fdffa79ea7c90f71a9cf92e205bb79d8a752a914f08c817901e4eeee2c2230209f73d383c714e10a41a02b0547283608e53afd0c96fd53fd420baa401004938058af62bdea9158af7aad5eab25ae631f7c39f88a5520b42716d0177ec5bf7c40dfe7840a082809080808685b0b6461f60b141ba4b241345a8011428c1c304140d1054c104cc4bcc4a2883d1144961baa882da0e67b2c6c05c78064dc2a711dfbbec4c29a88c11306cc066f6dac8630c93bb045c996ef07080890d3eb83410a5583624b84493c29205f68ed971d958ac7c280d86f67fc7ec765cec30b1e16f69d829aa8636c89b0b27064c2241fa3a56a51a9c2f0e6e69fc9ba0dca54d60c9e9fa1a2095b37ac9f81ad784615eed47d3fab1f9fd4a392545fa84696ca5577e051b5dc10d64f59588eb5299bba80cabf1f2752cf8a2d119e2c2c076152c873e43bf1d8184f1ee4a8d357296ceaa687859d5ee572fb1676fa180ea13d59fb89d1244608bec372788000113932b4a48911929429a7a5a22987ca658cd006a18cac4f7d5a89fb53b799ac4f6af55292c5fa68a93bf8f8ad56558552f9d7d7890b0f2a0b0b4298646366b40f74aafb64a315ba4001844937a316310f8449372a5518de7c03204306073600c78063be7c0142cb0f63bee810c60f5b6c00c40148e687324014f1e50b1553542f547c01b3a50a305b9ea8191dcc1624ec1428abfaafd65a6badb5d65a6badb5ca137046ca468dd5aa06c7cccc2b35e365c39542d1b86ab892c2715557b5d6d56a3503f542bd4559313313ba4e10426cc9626439cba6a5040d122590acb01faa89857d340bfba458d83705eabfd1a25e43b127425b5bf6a74eb18010b4b0df620a8d45a3c68d1ca8d60dcaa274f4b0495ab92cf5200a85b261c3860d1b366cd8b061c3860d1b366cd8b0b152b98eb5afd801c224df02f59282632ab78ff675d600750aeb63abf862857d29f111e513a68ac141bd3c655faa978dd697f0082b6c398d926a6bfd5e4b2553c980d5d67ae36badb556f0860543b8f1095184857d27974d615343a386c6874b542b2cac561182dd62b7d8174e874be77a552b7210b6926c93dd6285b045d8a6ef63358d4786a4b0d6da6ac5f7a562918565d970d12cac0a2949fe658aab7eb1b1d812d5fa90857defa30d17cde2d8c7f91d7fe3c2b1bf438af50171fcf7382e1d365e491bdce162edc8910327470e1e3972849023870839728c9023c7e7c8e1ca91a3a615e3a14ee15bd4903285e632f03f95ea716e5c2c009c70e3c6cb6dbc56aad50c1047ada25a4153a7b021045f49b0191fd22099f14a427dea51610d1abec5163663529d0236c395545d27d0488304f502ed67554b2c179818168dfd2336c6439874135340e82ad5cdc7033eabb66c8038a07ef538a45e354314a79f21f52a9c1550bf7a24d4af5ea71752ea552f1e37388429fbb1542ec6c1b7d6be8af5b52a8d12d5db174bc78d0e11fee686c7df54119e078d1c56846f845a47a84c65be9f5ac686061336063e0d1e8cb288f03c9e15eb521fe77b293a1ee7a5ecf89b19a240e2f1373f83082f1c9ac7791d8ff333f078e1d0bc70bed70a38afe39170743cd2eff89b97bfb25818accc5726ba7c7ff3588250df0b67051d7ff3483afee675258aefafdcbc8ed7f742daf13a5ec7eb8a8edff1b2597210daf89bd7f1d60a2cbcd0789006921410f5ddf870fcf77ddf97c5fae040fd8d077f427810c78bc9c2c45cb9e1aa4d5a5c56e6fba95f74d408c14f7dd28f65aa18368b85811f82cb36d998140b031f458362fa827ad530ae037edd62464c016152fd72c4d62d3e9415358ccbbe4f52b15833686c120e033f44c2c2c0afb971e5d898fd1d23ea05ab4920617daac3ac75cdfc8c6fac7925d9c0429b1aae24980d57526ba431f32a57d209c472d6d7fa52bf58170efc4aab1fe3a188d1353a982c618c37afe393dc06624e9dfdd9f1e0db5abdec7055315c3ac0bf71811f6b0a93ac97117cebc5653e82cf046631d62f388428558c8730c982654e362c1f9a07df36b9ecabf9243bcef05277409a3465adf8f22c2c5ca7068d99d6e7a271815eea9095efd80747fb35ac1a34665a5fea183b26592fb58c0dea69becf7ea171b90db0857259d7b1dfcf8d8e771dae96db586c8910e56558a75aa696b979f0ffe6a686f66b2ceb6f5ce02be9c78065ca3069f95cac2b59ea0e758bb56161d5821f4b40a8e36f3ea97e517d12cec2c0afffc5fab01efc6a85fd71fb3df8c52a161606d62fb58c9622ac0f0bf5594f9da83e09f55285532c0c7c319ab2589f1daccf162b84f5a92f298e14bb22848d3330202a5518dedce4d81fcb848da12c1722c047d17c3fe302ed67ab0567a09e956a7d1f3803f54a6a8d4853ec8f07812a2c362cec072c67d57888fb7e6afe53a96c84618d9b1b1aff362f1f53f3f22f07667d5830ebb31a1dd5ca12feccccab5aa1cb5aff25dd8c200d97cbe562d58460abc6a96583a6090ea779c1582dd6ccaab59ac17a5924d6b6b0ef4f9c857dff4a9aa9f5e4e2d2b22b2958ae1a911cc9127aeb86e7fb55ce0baad70baa2fe6311bc29305a760790f2594f8e2add60b04df9b802038f2c4101002b950366e78f480e58029d809045390e459c0a4e0c211e4f9923c0bd8e954eb176322045fb126c2fa6e4fefa7d70f21f8dfe9671e4b90eaf4c25961b53afd15950a69e55f2cf5ab179620141016063e0f0b8b251126791637df2389b0c99493f5b9eaf8192195160694c343e408482695c5862a56cc8a1096c475aaaf0f1f4b1048f3c25981c6878f44e3c3d7952868fe4af8345e2a241a6ff336af2b364fe3852508a4f9ff1e3c40df8fcddb1935ef35af574e0ec2d5876ff3493e0647f3b606fc9a9a9a9a9a9a1c9a1935ac19ae2b3db685a96ab86036165b224cf2307ae47c0c8730c9c3807d8d13347e86cb092d1f44e39d86e6a5dad607a4f1f69dc6a682de93d40a335cebdd9c7bcfcd3c2fb662eabdcbbddbc4c934d13c954b39e86091ea292eb53cd9d494d7d4138e3aa3aa7c803ffe09018a90b22ad509fc6af51c5674eb75053085eaa92127af4155afb59e4e402ccb7aadf5b34b6a4d555beb87b262abad298b034aada89a725b69c4cfa250b099afa0b5e0960ad66a672a0f6c5df9a982f6c3b526d95aad2a8ac5b1d5a2405b6b606b45d9ba42cd51eb67551b387da9191587107b43f55503545b51b656fbcdaa056756406cab1a51d5538faf484d590bf67c33d6d6588d4f9501ec6125f98cac836e9ba8e007034fb67e7e2240754175eb61187af50a565b715055f6845aedc9daafd61f75a7aa6acb5a6b5142d5046b415b793e91a5435542a5a1b1c312514f564705eb57dd1ad99fa9a05b13e54fab3c7ceeda40f52357d8afa22a98aa34f52bc25a95ecab405fb5167cdb5ac1eaa9d6140fd833024fb53328954dd52aabd6aae043d59505bfa5fab2bb5a3f9dba62d9aa4a557bc48a6045b0217cfe79ada9286b2b0d187ea0d561adada80adaeaaa47659d6c3dd5193f389628c55ad95a53b552b6ae5055b57eb68275490dc10ab13c2ccba6eca97ed5565b653504cbb229b07ea7afd69ffa55585dd556ad754815c1ce7ca9aa02abad55562b0a87b551415b4f60156259f5b322d4efb316b46015626ddd6167eaaaaa405b6b95d510ec4ceaaba00aacb5fe7cbd394145e443e154bfbaaae04d5dad642c187044a80000abb5289bc235bcc1c35a52abadb6d6aae329d7f90b582a191770e0ef821263c2b8e0fac2062d5c1730204a0bbc7024570afc480911e0a3ab854351a20a034071e5272b289144c6e80239d244091176b0e4f2e011a2c4cd8e903be3899f16d0f4f871cd549f16d4317c68651cc0e7c9709116041174685d6c8e1709c839cf73020a5c72c01881f6ad113e070de752838ad34e3ff88337b87feeee3cb3bc67baddac34cfbb94e63445e2ee3b4e02cee5238ce6229e37f229c6ed0ce489bcdee6cd2c335aa17077113c1c01c8fd66d676723bd4bd3682cadd65d84dd1a9887597d4788abb5128c2179d8a77df724653f3e17c1768027968029ba04ef89ae2410d61029a4ea756aa8414d4002ad103e66831f1046a004d6093158e191e373734ad5c005ff844606d544b954405849af986dccc9c3ed0a49aa2c9c6941a563f9f0174d0eaa2e95446d3a9b5c3044dadfdba40d3ea66864d0d39ed9c84d07283a583081bc01d9a6a5c35ae4fc76647756353860d2714544029225fcd4744c544d3e7a46707358382a2e963a1768039ac9eaf822d306726c90985940ba81da8195452cb46cbc6ea668523e5c237860da81d9f171b4e5834919004e67e464208a0ac5583b201eba7f3098146ad1c289b6a2185932a3add985151acef04b6beefb35fea537dab191ee0978a92a2516334639342a5ea970281803b6055e9f8706a588d707aa269061435d8a0401cd5900d601736bc66ef65902103146c2182301ec062773bfa610b92a0148afc64140c1cec9810d6a85230bcf0a4a88c108819c1083ef01441c41123ba3881161dc0c0041ae0c48e58c210bbed834e799003325c94400c072280858618a6bc20e54991922423889003471515c0d20006d030c32c041e191d6146172ca8926b2a830c1ff4600a0c3970d89ca00357640003139080037ce84100b3185c68e14911911621b80204162862890a52a045073880810b2480091d70b82107644e50020c5ca08a076469c22511121b1a3230f8a20b0f70a0025448a1801f78c0e52c862930b4408464882c6643a345083c6080237ec882cb1854204a826488c3e00b2d4200e68a0a502185031400440f59a8a840546488ec07484f8c860de865c0e00b1680f1c0151ca800150e5000103ff4c0431617cca84c4961471423084d98d4cc58018f2d3be820a5051e04f0a0438f298a55191f19768c53102c10544f800bb00ab04e541fb6e7e3a9224be743e17b1c23581e1527b5e3d3516fc01c15c7aa060d1a5f08d6b46684343b5a3a583c666c5629152a6551a8d3e9ab8eca3243c2079a52abd40a9ca1f249adbe105a483514bd5430d48c0a086cd554b0859a81bc8fe6b301b7b8c18826d40c9b5a813934210031a06986855609e6cca8c102c13945012b8a88069a8a3419b2b9c182336093980f9a6260c309616aa82799cd08476ab0e08e4f47575d464e2894a009851ac128cb8d9a201c3e253504a049c7aa85968d960d2b496ee8547aa6074d44f64a090d8830b5fa684e4aa00076a84105ce4085506b7ed4200304ac4e3041165519226658d6e9884d4a08c804c3e0c5861a27d84c9299125053c01352426ca080124a6c58800f38429e2c3464930158d1c4dad98eb881c6474d6581c73724a5c3a786999410d48c6ac6f743a4e40b6126490f9a6652443447aa25384892ccac96b48e4a221468ca01e6f8cca064356850332c24af1812d034a35a22424d87a6d3179e2a6a05d4cc6908ea042341acddca04d512d44c4a07cdd0aa0758b4ca618347093b709ca042810994126a985189a91f343f7e888c522d30b5e324fb7c562dd0843f925aa566c09d131310e8483de17cae130e9e958afc84201972a39ac1b1faa941a888054405037b803d500b683a6106387442e124c2aa07c8a3a6e70bc1862671a77eb64f07fc4e32bc6208e202196d0003c27cf1d280053031002c333069251b2669314c79410a0b518e8c9aa8b04406f4035602090078f00a0e9401860d64908211125e3b74a4b880200b2712b00292146421dc68b15056544145145d9c60620925b818200acc841df10823908eca10238cda0b60e00003b36c03840fa6cdc8909954971fb6fcac50b08502bac884f8e4b0c19a51a578e04c40025370f9a44805a2244864407a62346c66d08007e8410001a0e145100b3480010bc0d2020b3f3e3736401184d9010b468af8ac522807e440c503103044103ac890c4010ee0c3932223434c68d5207c40b06540328f033b0694816dc1c98b9a027b026b8255095a62ac08ac16b60aeb80af0156094caa3dea08aa104e3c6874d81cf646b56153e344c3dad4109c6159a719bb52a96c2a75b260fd6a45b9fb8c0f091ede88e167ed6ed44c753bb8cca489eeea7713c66fb868f3511ede54096f4c772fc1c31ba1f006c8696fcb1f7bd83f2e4d8d9aafcddfe60fe9a537bd38f2aff9bb57bbc971930a6fbe30070f68b4bfa126c6f13ef645883cac089147cd1bae511145118692440581f92928ccd1e43a15d114ef7d753be9d60e7ed66b88a3080e186a6e8dcbff9dd1d738971f836132dd658d63e646185ea278dfd737c494280e6f20e0c696c7ed1b8acfb7e5c7a479ea4fb32dbd5baf60ab1dbc4b37d6e7b5b983eeb1540adc7d04f7930dace0a181bb909e37f269ba234f10220448c88f101f2141840011f243880f213d42788408010202fa01f2010a020404e807900fa01e201e20213f403f3f3f3e3f417e80fcfcf8f1f1d3f3c3f323c407c8e7c7c7c727880f109f1f3e3e7c7a7c787c8404010af213c4274890204082fc08e223484f109e2042800001f901e20324081020407e00f101a407080f10213f807efcfcf0f911e407901f3f7ef8f8d1f383e787101f403e7e7cf8f808e203888f1f3e7cf8e8f1c1e343480f50cf4f8f4f4f901e203d3f7a7cf4f4f4f0f408e101e2f9e1f1e109c20384e7078f0f9e1e1e1e1e261e77b38608742ae6762815771767726660b0197e86999c1919fe735e86ffff77771d77146235aebccca8c611f797853544a8910a69f8c05f36f3b26dc3e7ff0c6de36e66fabf6df8a4611cb96fae88ab4811773f21364548a308f7d7e6ded5feb5f9283e65ba4477b9f6b6fc7b97dfdd6126b8cfc87edf2fb11269a25747630a0c321a3c7ed6eedee568dcf09795b8f6bdf2e2f4bc7be724cd698a8497d33407d97049d6ae10209b2edc5d1477ce66f34468c3c429277632c2dd713cb4610a6d807c89eeb2ed759e46c5e7c7e7f88081f6f886def4316e3f6a3e36cfdf7a977bd946cd199359de74966e3dcbe5d466b1a0f864ba391e1b6a16dd221474441ef10240f44eafce02ee264ce131614aed8d3c151e186cbb5a15ee0e000f4324cf4205770b867d813dad8ffd31c408307ee2410bdc8b18a2c96b828e78cd818bbb09a1fb37c6dd4b16aca07281d15c447b349b5bf72406eb6d5d0ecfb429da76351714511ca93439c556be577b5c9a4b66f979ffd6e5f0deb7ce3420d6e3f344887c0fec89fca3c0802716a08019dc5df490a6cc4e9be9ad0958538321308052d1050f05431f0f0ea043054048f1021c0510f95220e6470e0320b1c3142390ef4b0f37247662182ac3879caf090cb8be38f241b1029d1e9f0e4d434c62b0e2841638f0f87450c01e05b52161c46c7c5164101e110425906819a0f50d392264020633f8a12756c1efc38c0046988016b2e488f1a17478a20730247004891d61d81a5460a949132798180a6d60c304e1882b2860c28921225f5809744181088ea6bc00024370618f700197087041c4083c3231b6862270bcb003195c475d64616d597c088e20c114a626acb04db488b1831c3a788d2ea8c2f64c800b0c1c21cde0022190c2e2e8c11546a44b1491c41428aa184d1ca101202a10b203094cd416b020491216280097d90b90a81d20c009524c0102115c20204485028b20471cc062082a4576a840e400430e40c07004073490a5f2a440fa4289de0a6e7857ea141dac9864906206251c760d42406e89021801064f85b39a90828a8372878e071a9495060827664e02706420658aebc00a2a21fc20a84a084620f916377440d5c38e27b00adc2de085051868072ca2e832e408b80189801e315604c10422ae258927269490c3881712209e2bc10880987862064db81c3ff281061f219081065374ee405f8808810c1e48dd8080bb0835a42084a98c10ab2cee5f144c35e00014b5561963c68869222c2304481c11a30c0888600239a4b84007ce09a38c02ec504516ab6ca4250b1b9441431530509540d8eca8c1176590630420023ab8c1c1cce0a20ca205f4e85100530341023165885f4c09ad38018521a82ccab05180145c70451819b0406105193f00811712a2e09e1e09544146170350912983819a1b4a5290610505867441f8d0e5082da0204301651430d342cb9e410826c8f021015d582089296030850f4890b1bbf801af872e4683058420e385218a70d8e1072a4a88b203192978a08a32684032c07394850c1202509b9dce2b2ae870858c990844208a2e0b28a3698a3d06183b7041992058b0811c94708e5182259eb049a199a5a007e51854a8008ab3c70cb526b24c19834b104a4984a00224786008a43170e0c00a864cd841021050d1640ca52c5888e9620457a43064680ca30700dd8270429007f440648c206000992922c70950c000c818385c72d00204446080883072c6f0136c1c3e40820db20ef41023065b86a041065a52c4131e627c4003a1103e4e82c0c28f0d311c6003111a3d2a1208410b2d3186604016443f1c613aca95018a21030d9078f10080031d3e208618d70055a0f0218a0478200261889184042b4c81f54431e2061b8891c3439811a2d882450c225f88617301167e00635432418b8b20f4a00c9e03c4b840155c3c1113042e9ed09191a303465c01c6dd9d8c2c82908118ecd0e22bb2034917eeee34332a938a6f68c1bd2ef570af555aac3ac3042bcbdb8bc1702eeb9936c518acb6d3b3122b9118478a1bbdb53a103260e508894809d1d04c09d1d14ccae8e48b18f9180cc5a768dbd578a76ec9f50e357de7ee3b1ea276709e37f21bbd39b1d4a988e6db4dcb9dde0def9b6ebd43e2d2c73e6da7231fccb800230461a037e0a2898b0ef8204a15301de01f8091238b28fa0b5906d20c2241247821841b3e3da2b8c2e6a5840754414b40802d6ab45001d529b1c0e80a1a377602b0050a92482f408200c0508478a5ba2d9ac108257c09f88145471520f092022b402e5424d082073600c0fe62a7e606d0185f17055889043052fde6c93783147e10460a9c20e20628d00d4a0c248d501dc4814e6e84010728c0a8010039e82067c09d17440867d540035e7b74dc45c8d2e41fcd11f7a2774f8571772c6c51137777797822da379fb659cf2c77723b14a799e4f92246feee1397f865fa6a7797e2219801af99e74e73ed86205217772624dc79b6b87b16f7cde49e4e39ddd12920cddd5f2188e4feaab9eb702772076dc0ef73bfe15ec3dd69b887ee5ee3ee3380883d3a1aefde79dedc51ce4829b6d9764155f78287d60b5c6612973c62109147072888aeedd87a787882f4dc5acd878ff2a7c7e6a3934d51e37d5352eca55b9b379ceee45b7ac99d1497a7b9abede05cc6edb83b0d1220db8e92cb69127767b90a674bdfe6a9d34fdb9786c3c3eac5dd69786881dccfbb9473383d95a076c0ddab87f50a77cfe161f581bbdbf0b0bec0bd08132e5133c51a35752a9639b743e2a5d86b13f6283e9fc8e3da97e72ee3f4c7dcdd877b45dd5d8787358bbbeb5424d19e9ec1bbf32e3d2d4d91d01ebd8fe6a7c560ba9bf2d09d4cb6cf9dc650685fe2f3bca4bbbfbbadb9fb8d87f5752aee9c6de5ee2a4f739ae247ef97184d771ba71bc5397cea72e66ff3a6fb6fa8b9f7cda5d9f6b7005e8f70f71d1ebacedd693cf424dcfd86873e80129fe6b5bdc6bb57fbb7e547b3b9eff7d29cc3399d8a1687bbf3f0d061ee6ee3a10fb93b2f9fb6142fe5ef6d93bcba4bea5424f1d28c77d31d32d53b643ecfbbd34b6f5a334f774fb93bcadd4fad0f74773be304172cffd2d434fa37f3041996ca634110234646ad2e2d5ac02c5ac0570eaaefe7ca151aec7f4f437df0af448194fa99bf9245cb079d90507f7a1a56ffba92c3079d5e576858bd4e8f7ad1807ad54bcb077d9f7ad1807a9d5edffbd380aa282b3686b5f5fb98b0bc0acbfee95465b1a106fb16ac0f825f8d00cbb1b894ea2ba1aa7ce30a0441d0053ef8bd4af5f54bfa6a7df0fbae24d547815796ecabf6f7aa577daa2b29d56bd32ca949a9ff9450ab97120afc93eac1d7a7b2af847a3968df5fa99ebcb0bcd624d5afdeb258be7a3fbdd20d3eeab5648f7ca0abcacda8847a1f675e55ae54b91951affd2da9a3eaabaf2a57c6d4eb747af015657a496d962f80192de599d1d75d9c6aa169b3c418d51aa7b79cea1ecde8a805e601e0c2fd16002f017821003c8ed3dd46f1d289750ee3764e89f1466f7a77b7df19cdc191bb2374a7cd1ccaf80eefaabc367360c25c4ab17e8ce2de69a637cdba1cde3908e5a072176fa8d94bb70e4ebf57dbd996ceac715aa277df4b6eb344efd5ed705881fbed1ae1dbd5e1dbd5612be6914c86840393a77829cf7080e2e9124e75d88aa96fd7c8881322453adde51aee666274d62b2f4eb7bea71123d88a79f439d88a79a4bb7809111f3fa2e48d127d0ed04e77310e5b318feebe43e5c54bca8b8b96cc7af7c4e952d6cd986ece0812918d8f304e9182b2a8442742526e00e3862f4e65ca3b11927283016e38dda94cf99cd27c18a688657a79282f67273219122fa3378c58df50f3869a387d979ce715b2210c9a712e6b1bb8b868834e44b10d47dcdd867cf26ad8c2c534bf8fc7b51a9adcc71a7886a4b84c73d318f78c8e4d45b8d8bbbc1497e82ee7735113cec58fe134e760df43ef36a189c779d8f6396f2c5eb8d77e9b803e162ddf3bd12746a7c626969bf3bc42348c1169586267f466eaadb199e6194e5fdc0c657a66e599711aa7b83d9b010bc7337099cdc0e4e63e77b999be3823e3799772aad3dd341b218273265279e65c4e6d3c8ce6ad67338c535b182fe215da11b1bcf262897917b765f0c25d8c5d191a20034de7f3f72ee70c21795e3e71edf7ae26c34dde64ac7c41cd9c152dd70acd734ebf57fb8dada850b327a6d6d3a107a4ebeddd2dbd2f966826b1662a23c64abc615f3271a9d570f939341a13ce5dc438b531bd184bb3b97471b8a6cddca7d9c60b93e27dcfad61af6fc8f3c263ea9de7ce4674de9d9168bc25ee222d89158c531bcf71f9e735378a97b07017699ae93ac1b98b2e1581bb99b81a0b4b46ee22cd4879e62548446c4fd06ba69705da9767c62d85dabc2dfdb81b93536c65d7c5ef6b349bdb2ccd5d15f3fc5d93bb8877282f9f3b20b1dce4c5a5894d9b59e6dc4ea583535dd02e81bbfb799a4a3b4818a7b6190fa3431bc5b911a766698408895990c990743ba59c9bec23fb468f71f7999ec2f7c5e56ca69b765a9bb3d9d6bba4f26aa3da6c63ad612e52a1a54038ef4c87b1d20c4a490445a3595f9c2a91b94969a2d84a4e6d254e6dfa76cf2437dd4c48f4ed9eb6d406f737a2653224224c39485fa61ce4d3b33bcf4b9271c76d9357058bf3bc332eee5598964c5cceaa18dd2a3c3872df5a15550f2b41d16d31445416e9b4b88b39f3b509dbfcac3f072f99e76facbf8799e87062d6017177715f93cc5a5fddc34a9f8b740f3391c9906c39a73a5ddec2dd7b5869b6b1ce5c3216f3a33dc836948bf330fada447369163212e3e1f1f909627ba2777a8d3e67c4291ae6c2b8e78ac8e95cccd9b897b8779e62dccf254e28a78b354ca23897cba4d217b134cb9df334794a4c4a40ca9b536a255d2089c95d8cf54e9d2e63f34caf8eedc4fa3c4d5e1290ce49eea2364d2c4af49a5bdc455e46cd9a8b38b74d5eec73609a87511c0f5cdc24b6d9f063a5bb4d1c4e8abb8bb8d0dd71edf6455f1e5279e6db16b1bcf9bc49b9c15c44b295b185b1f56ea8a9cdcd83bdadf6a8f94844d29cc4b6e3ee1ac7c0ddfdbc3b6fdc347a887f44a36f52b2a0dc1213cf7c4f8c3e11f1be8f7729059339d5e697efee9ecb413c24bdd82922491c5923e32ee2c8f4625dc3c25d4c917858dbda3cb3c6e9d55ba748b59a4a3c73be53f8ad8295ee3efa9dcbab7466a3c7b87d63b42ddcc5cd84d18ca2e6f3f0d18bbdf373ce13ebcb7bcca3797117695a441a4d9b34203f75656658ccb4cc8c66b01c7a33cb18ac7017b5b943cd8d667233211961c2a816e76134065a0c412e0f2986eaee62799667a642021753ccc3686f671ccfdd651e52e1c1451a4e334943cd1b8db6ef631c121523f1bca899cb2815176928c6f16829bea1f9fc2961dc457df7142e8f5e52e74c4e619a72a3b3a3a4ef7e9ce6dca7f9319a3fef110632230c5c60c031f1108610ca0f0f5f08e3be6ff9c27e01002e608c7eefe6943cefe682961b6adaf2bb407301a674ee82de96a578f15d44257da5e8c4129f2726d13b4a81e9a0196d81cca7f7dc91e6d8c2177711dddd6ea849b6c0452c51fcb9765f9b630b52ce9c8dcaf2b6f0a9cec4e914a3bc272c8c71e76116b4b86f33bde74e6b1c79f1eb1b6aea74f7ccbc9dc91f43168cfcbe2d4729b3519ceb5ddd794933ca14ee62695e5d0c35bf77a2f961463c8ca273d4cce5ccd7e6cd6994253c8c8e5152a430ee48e65dbaff19c5a9908748524a143fb6dd4f3312eedda3303b3cdae2aeb1be484740eeeeb5abd15d69e625285becd23493af4db8a70e0a93fbc69b34d3adff7e9a6d38857d120fa1c0fcccd9088abb3bc9845f9bffbfc21747c715b6b8bbd83b6fa8b9024ecce16dfe631c52b80213a3322e92e62d9ff7f5fd231e1a79116fa8896ed4cc3a2326f7dfb8f73ba346372e626d428951f36e349337d454d237f78f71484fcc53a7bb45687d75bdd4d431e12734bdb5c9847fe7a227404f542ef6769117628971e47d7cdea22a448c66a534172d51d4049f576f268c3ece6512f6452138b9c2c5126332a73abdc36d949751a58f6ddc836533c5bad7263eefd1e3340bbdde658cde8ddead51a2c738a43387b7a9996eeec4282fcf7818358244c4f6a44c3389a1a018c7db79c9d4e13493f8dc9518471e95379f4a69e69544504a9348495f24257d7792de36cf8b9ab630f89c6dac9b78d182ee9ad0b6b9d3ab6bc2e3bd2661ce3a2663d21dc9040b17a9d0987061c2e4cec4a897e6ad4d1c13d86ea64299990a6154e82252a16db3c42939c3fae2c874a753a1490529e45581c7ddcfab672ae8725a12b10097bb33eb72aa9b299dbbdaac774f0c05939844ef52798dccced3aca5d84897dbd566e7e561b444f10c35cb8bcfad53d356e6193eaf9eed5d3e4fac6768366f38c569162acfac671aa7999c9d179766695e5d89e21cdebaa7b719a4a4af139c66a15dcd4c379ac919de77c94c75467438672261dcbb3a4ca2f7bc419834539ddeedbb8f84d06c9eb797e67d4bf4e690ce9b4b339a6d28d1904c86a4c40b39864a9adcdd95d07497372a0172acd35ddceba0f9d1274a6edcfd96ba1d0d979994d1929071777d7949c2f8a363121dd617a7242e79f93c833c4c5263888748b6707731cd4f4bb3d0795ed24442de7433e1cffb9320e9e2ee222e33b9cd5c0d89d12ddde9fe869ab8f6b99c7e4eb1151d344412a278c806435d441e464bf4a63b1263dec6b527b14ee72e9a8287433417f13675d6e532f93a68464b8c4b933477474345dcff739111bc3b2f0b39bccd1e6662a48799f4b0122e33a97b58a9b6d3425bb84885a6f1edea66ba8b71b38db5d0142e52a1096d61326d38d50919b90b7d50190fc1c3a030e7692a057599ed2026910a4dcff62ee3b6c99b6dac8396cc94d29bc369186483cb9bde74bb5989ad9869c6eddd9df54e9deee2f45d229321e132934690747923d9285ec2585f9c11d617a7c3249a51d2b4d976faee3b74f72d37bacb42360c45a7374582493417c964489777d312b74dde6c9b491bbd3c23b1cff1f103e72e8a73179de17357e65c9a5173869ab96c4389b20d3567256a2acd6644454a69e66d8ca639e9e666b8cc42b8d6bb4e54c0b7f46e23b844b1de283675268953bc64deb6ceb9d9be33cd7473256a32c1bade9e21c1676d9787cef3921b2b91e6c6564c3d9b31dd1c109f1dd3cde9349b29de285112739f3b273992c9c82edd1a25224262167ef858229223a5c8644096508cf7dd1aa72f6eb6519c5eddd0d6f866967ba7c3e9bb64e79cced025292e2fc619414284c42c68d2d4a8996e266c04684784c42c50e1f15132e112251aa9f8f8f143807630b83fc0dd691e16998197a7b9e3e9117de8641b6aeae0f392bac4e8cdd4fae2f6357a22936d8df73d65b21cc9847399dcbb9cde50b37775e7dd6df2c6604472a4148db16deb4cfa04f1b104a72f0ea7ef921c29a53467b319d3cd9de7258176335d8e9492e634c5255169ce88f8400428a3b41956ba9bf637d454babdf20e1143888743ba0c8132c4cf5d8937be95bbdc2e6732dddc5fdcfe14ef8bd3db6bf36557c87462ef3c7736ad314996fae2803c948520444c0c4d730df6dbd448223a0ab109097d5fa026770732729c3651d3f6681e8142f8c1c2c5f3bce4fdd1e2e585f263a4bbe97e14e7cc7b92b97ce4ee4a3cf4e9810f17f7dd49a2a6cee4eb6de6306e9b6836517314b7f9254e339a713cbdd317e7837347efdb32121f180fa39feef2e7bd6f7a3373416810448820475ca4c53ea724fa1c180da7bb7dd19ba9fffe0d35972e8e4477b65d3984769e5788f6f7f346cd477738725f9d36cb256d069901648ae7e5d79777bfbcb811c8520804e62ee69e34f7cde5bdbbe87ddc6d9753dcc6ddcc12e37eece047123f96b88b68d6edee97e8ade5fd399f93e27dcfd738974f3cf441e64b14fba880bb47f1d087cd47e8e2dee52fd3fc39785fd23c75bbf2afb9cdcf49f112466fa88923f7cd5d5cfbdecdbd7857e809e3ee2c78d863a5c7049e13f09cc0dd8f3ce4c912f2f0b858e2126314d73ee75b2da7bad7268a9752bcd18ba67829dffebc3bf43a79b1c43a5d4ecfd3e4f5ce9aa975d63dc66dbdcb29bea1b7fc12bd1fcbfbd36ca6baf2e2f486de47cd87a1bbd7e66b1387249638c5af51f3e6300ee9c5b277ea5d369770aafbf4b589dbdfab3dbedd346f8d9a5f62f29e3a5ce225139338fd5e8a7755bec49bc43a26243b5bc46839524a89e223bdc3db966137c5bcd71827d9a140b8a373519bf8865e1ca977af7579eb5be2adf1ae0a4e710b3b462e6e14eb7107266e21c69c0849d138d531e16d8a3711c3442e252671fae3a14873b1c43bc54b252ecda59cea9e4613558e7369ded7dc266ae24813c7fbd2bcba5d159c22e98411d1fc5a89cce77e7d71a10e530fbd9f66dbcdc4295ecada7c74a3bb4c92f751bc64de1ee390748c5cc41b9fba5c2651d38990943f2f999be8c0dcc52739633ce78be3944433a9836634a70871a3586ba41c2347818c1299cf3f4f73ebc7e92d312649f492e86e89872870114bbc71aa4bf146cdc7b5cffbf1592bb1de3ded310e0985008428f0b8bbb8cdcfdb96bf77799fd1bfafcdc7a9de5d12d7be779e37c528be7d0e3e6b17475e9ca2bbd79757e2d4d4b87c8c430a4ff062f7b99cdafeefeb8bfb6d3e265153e33c3cc18b8b29dea769d368361f8a87271879891fc5b8f678e9e25e7f0fbdb9cf19c9dd9d78182be3369cde93c4e5e79d661c895e1d9c16f130e6c5ddc5bb6ff937d44c33f918b795c87c8e616c8abb889a17476e9d7584c2d80d16ee35f2eaf4dbf22b91f7fcfb5a77712936124b14eb47cd8fdd5073ef722a62d1843dde696de25cd6af2fef718a7148214c27a239c5ba9c71dae47defe2366e9fe7853dcacb8f7148210cc8ddc512a3662ec51b978f6bbf334afe6b131ee390dc7d050f4d4881c6d8f61bc529b6d9ccf271dbe4bd7ebc730ea7bfd3acd3edf4ef8c7e2e3fe751a21297468cc460449872d036736693991123e7798570cedce80e8a4c66e466dad02cf4fa3e12112239520abe5dad71ce8891b1858dbb99983763bab9ad719923a530e5a05e7a4dd2b46114d77cfcd8e5f0366718dbc212b6ec73776db82c4157a2589770a4849b9d661d3ef126618ca358a7779a842f371e92c045a442c37b4762264c028e0420f71cc944822aec1146a442d31b6b7d6729b669f4a67b869752ac713a2bf13673e619f608c2dd5da4425b9ae19a9992bab6d3e86e86c934db64b28dcff31ad160788c8f528cf232908f9edd467739778d68a6ebe47362403e7a769f9362d3666e1497444230222466a1c425bacb413ac5b99d4633996225329f67ce35bc646ef2ceb499e2120ad668d6699cee6b2406b4fb1ca01dac6733774651f366a639c5b5dcae4889cc67d0de659c33b791262546b3a953cc0291149b29de55990538bd24fae477644cbf2d8b5468b627286ec2749dd060f85ead77eaf3e6a4a60e978f7739df7e7c71a358e74c7cbbb9e7c574d08cc2b43904c5280a52998bf0ed1ae522283b17d9d8d009d9d85862a24a506c6a78d143efdf7447e2749b35b8b87befd2683635ce5e794f9cc669129b1a4464a91cd4086d5237363450e0b848a7226ea631b6c96439dc86860a363486d0987177b1f7328c6de92ee8c597a5394d314d264b33ad8799e84ca4750b78ebdb44df7d87d29c4429e720bd82ee61269b091745b109a3b009a7b8ff545cbd29b1a921e3ee3b9f486c6aacd066ce6535575c2967dc8ccc3adb763ef5c569897755dc1dc86d66c02e2e65e0ee363469ba2b79f9b4bdac49b6a136343436ad226c5a5adc6bb06919ed0059da21536d737755c525aef2438e8a9e1021091545516126657492d128473a26ddb8f1518af191de9dba747704a5b70bbaa16679e6257833218132945e7ce65368d4e5d22528d63d11e2e1225d2e5d8234e4634926cb91526c667e6c6672dc73249383516c5654d8ac6a50e6584e0f2bc16c5655dc7f5fa567c2688e64b25179e17e4b2f4e284d91283d4182348018dcdd749b9416152ca7d8ca797edeafb7597b9b89d33a3a15331944f4117fe8e890daccedec90daccc9641a5504ea8a0dea8aabc70565859e6a51b58295550203b815f72a5cd89c409b938f1791b8cc68f5012a42648857991020f7fa537dbac4604af9afc4669fa361255cf91c580f2bcd7ab5d9ac975ed4ac891a3541191429f2e98eb4f9c4f80d3587dcd0b7f9906cbe232308e3fe3270872136193d7d36b68bbb8d8d8dc17d6774a3f868684c22de7dcbc7f888f6b6acc91f87306e672129bc12bd3c2327516ae526af2dcac65a7c3193a8a9d3f492af4dbc74f597e82d6f3e6ffab913a719a77517d5b56b9eb5bbaf7813f1e7e420d56a57f7f8bc2f6eac71db7c8d5e9c3b6f8a7318a733737fc95d99532a341a0c5fa2f76a333f3abe48c356cc14be8517b199ee1225d1bcb1a642a3c1304333ede293dc28de55c11acde4e74b9ae82e97cbe4eb4cf230bacff334a920197a222493e91e569acdce13f36432994ca761a0028399f2ced3cc799d7cdbbdce59bf1e5fccd11b356fba49acd338e56134ca8ba7d661ac4485a694f312bdd328d66816a2c1f058ebd4f2d6ba4c33ef51221d26c97b4b14931a97dfe4cb9d8bc69d8b6e6650ce48bda0121be927b31d48e1eeae149e369b6d7cab07a841dc1d04e2fe75e5eaa578093f6abe0ec294e8ce76840e02a0039abfac4477369c12d1418f0e68fc659b09e7fce7bc6cd6dbd564322d74434d1da5c4649a9320a1d84a4e9394514aac54a226a93114a2124a5089376a3a29314edff3a88512ef6ba6fbea3094281b1f6d9d9b6cbd1b2a312f972812a21ca4e0e27977a89933b7c68fe2ef9dbf73d18b2e8c66a59b4b7779e9dafebeedc43a0fbd0ca3e6f9b9bc770e7a31636d7ebafb8d6fbd9bd38f77b947f4e2511efabfafdb3d8a7559a6a4cd34dde59efe5ca67809f3f2921769259ee17447628d9a34fce82573728aad9827a427fc2ef7de46f4ae275090bcf045e312a487820469440245c923097aa4a3284e5e44b12e7f3070fd952b5fe22f62e4af5cf9184c9b22c6a9ade8c512cda91de91d8df6f735d6bb1c5a2fbdb834f50dcde77d1aed758eb6d9322e6fba73414fa3edb20b33fcdf7d873ea74c8d3ee78848cec41de9ae137cbb45feff65f834dbfe334aa3d1903c00fed4e52e13be5bef5d7e5bd6db44773308c10c6e104d30e165a539cbd1369c7e4ef10b779f7868a83cf3928b876ea859bb258a754c485022dd44a7a24e8e64dac9914c3b37d4bcddad65b21cc934c6c8fd65d98612e95404b22493c964ee7e3d94810d0e64e9f37e7ca6d99662dd794bdc2b2f86a2cb21349b37a1d2bc4639bc89d0dd0d07e5cc7b0a1d21c1e57502e5ccd9a8c42551898d4a9c96184a6904833130f8226acd749d885f128925ded908bf58e22fd3cbc3e9f9e979434dadefc6375bcee563de2ba599174377b0174dbdcd1c7c5ebd7159dbe9f3c4a436736fcbaf718fbc377cc6f4cd29e9bb61af5353c784e4bc3c8c2ec945e769ee2322349b4279282395689471e7a2dd93f2cc47689432c54b4f72510b42a599596042d23bd38b04ef9b974ca328ee164a89cfd364c2c345252eb3d0511730e8c2025d289de71532020a7274246e25d4c4385e46955eeced14eb72aafb5712a0205abf1e28c8d1117a7bd104252dd48644ac949750536f9d9abb54f75be3f2ccb0d761ac947b27df448b5aa64bf8379f8673f988f6224e8753dcb6a13ba36f333f067baccf9be4459a8f1f3bda6bf369b4dfb7dca5484fc3b98b9258a7692f6e9dcf8c7e264ddc7eda2e6d262d96437baceb6d8d53bccd73639b2d6b13bd3852e3257c9a27a9b37137f37c4d9a5b9ba4a91b89d8325efafbbdda6b9cea700afbbf4e9e66048988ce9517a7585f52ef926c5967eff2e7db7ea61cf4629a7338a7839a7f59d8f7b4a1e6636df2ae0c09d293a2175f4c71ce8b4b5ea4d18e3eb699709983bd7e51639cbe387d9de4dcf2febdd39d37869afac521a29dad445113b7735189cff30a0da199cc484419499718179158778473b788c4ba2322d40c4270e35e7f541fb8abbfa80436628280031b884181982cdc3de5a1180e88a9428c13629a10c384ebf2c74880fdcec748f8cf21e165bafc39243cec73f4538101979914838a418ac1f6fd9e2ea71bc5b39b6ed49461a414a7d8ca0ccd280996acc2117881cf5a0fcda1c5981138616ad4fcaf091100b9fb967927a1e6ebdee59d57e76e7a753f3f7ef498baa7455003112c89c09653fc029a8bcef386a1e1facc59e7a723ee1f8f1b29b14e67a65bcb72faf288b8d799ba3aaf7b55b9bb2ae5eeee4edc95782d62a5c7c45bda6d9d56d16534a79464e26e2fb820a50516a2201d4159c1a8c84913262a2c519204c99050d0b823eae494514619376cd4a0615346b5652144deba5e4a2e6561e43a1567d84cb799ee9d0f9071473fcd36327771baf56333dd2fc3a2e903577ca0cbcbf0ce79190cefa30f0cf181268fc1ca2099ec869a6789de251ae3246ee4fee4035e12e1349355ae70011829eeeeeaa548454b7c9664b2d9d6a63e6be60ddd2529b112999b40c1682e3ada3943e15d28b3141b798009b12cef97f83c470f5cf10026d1ab93620fecf040e8eeff211640c06205eefe46fe6548901a80c502b0d8f2b2870016a5bb77712dd1811df866c23a37dd5dd79f17ebbf5076236ada22f0329d8a3b3b4a24c6913299de4cb80325b8fbcb4aaca3609c904ec59cbe3cf2da7476345e32b79933539d6e3321b9e9ee1a651b4a74748591e6b9e9ee5ef1ba87993c07c4b87b89f58e68367b590e7cc0831ef0200c1dec400739d0410ec8801184155270e0eee00dc20d9471f72d7c0355d036104446f7e76c409734da9727366ddacc6d40c90680dc3fe765be4242035cb84ec51b6aa65bf7cede3d75da1cfaebee3670f71ab83b0d66e0ee22b89f44d8e130ee6f7a75a71334ee73b609a8bebc4faf4e676000ae5331c54ae44d9196e8d94e269b91e8e895852f62b8eb94e635c2380992a8f3d6995b97336de6a9cb5dcca3d14a5d6e57fb9c7cf462d6e77dad778f794fa3d17e480c851ff2273c0a8fb16d06fb22af33f76ce373484c676e18795e7256e47fe333cd497e67e773b6dea5341aed459db9bf57fb9c98cedc9f51d85b31b54eef4cef69e6d05daab3d14bd2ce9b4bf5b923cf4ba25967eebf77d768a5369fd6db26ed5353e7fcd2993b35975e8ff74e97cb24edbc4f68d8f6e37f2c0bdce88c717795bb0cbcc4557e4889378a73b5e6eea887554c71f721f2e8e894b88a17959e5461c35d295740087771f34e8cbe4c4911511194b1850a34b97b055ce348bc7bdb4caf6e279749b1b6c36df3b6b323d3babd71bfefa7ba1f52e214df6e66ba7778c9d428b6e1f26f48c5170c80c2fd653aba1cc9b4a377a8e0b9fe5196680ed22115b25c49a47b58e9ba7b0c60408114b83bfe9e57f2daddbfc8359408a75908dbee918f1f51363e2acd21ddc34cf4d6b74979f1127cb435be11ed7c1ef17091e6e5a292284a690e193541893414a1f284a2cb9152f4d0d67989eea5782993da1cc24df4798e1a9bdb093e75668a9491caad4d243952ca4671ee868fd02825def786d3db6dd4293eef5169a6295e7aa2cb0d9550304ed24bf19279e2a1f14897e65089e620a11b6a6ef42ee11343d9bb27ba5cba64ebdb4428c5465a974b97949844771945c79dcf239cbe4b8c6c39a74678df7d8451a2a3149ba70ee3249b091795172f21ab38a9921bdafa36c165163212cae5ad77422de872e992bbef109acd17089598878ba4b881bb3bed310e490a0bb8bb98dba1f86934296e17b7f4f7b510d2795e21a6eb44fc346ff226b9a1a68f1fe56f13dd7d8a758ff50d127dfce861255a2cf6399f0383d9320a0f8466c5d47fdef29627c6a5d9763fcd36d296f5ad6914ebf2dbf2ef235e6e9233520b4cd749941b9ad31672d1ce4525bacb49762e42898e525ca64f6c4ff6ee490fbd4e72463ae2e124392315e5a2128f4e64b7b6d33b3ddd59e69ccb0b173a1553ac3b316e96e299363546f1cd052d70b1c4555e544ab34eb2629ed74cb1f6d2e42f2b7a72a482175bd8801c889ff76754c76bd8801a0694e12fb3e112e3f2267a91878bbe892d4990c1e9bb442623cd14a3f8a6d35e74e1e016078d70f7183c64008ef7f4f4883c628f0e9a75d20c9729c6fabc78979ee74e278a243ecd3820e18231a280edecd4767ac7f6b8fbcc4328a6d8f7662edd8a8b78183d1d31033a2527a78891221f632a4dd8b6e1263228e9936dc34dfeffd129323c5369cef0adc43325f2a63b8712b242a9fcf40330458cd8f0e98122445078149fb35979e66f0520172012291e5b975fa40899eada8d028eb83b2e332a43f1cbb820d1f33ab613eb2f52e4457cb33eb83b0c1e76c982ba804424341a8d424fe4994a53e7440ebc976e14efe86e6e07e374d776f499c91d1f3e42807c7a786c3f808290d7a7a7f603fbf8f8d87a4a5b8de727080f9010d2a7e7470816f2b3436ebd4b77c88de2a58de2a5d946f1526d37d3e54826ad67ba34330b32d96c363323674664b219ceddd49662a59c06f5b6799a3b8ade9dbbdc4e28474ac13bbc5b419723a5688d8f4e7c24a4b30d25d2e5ee36f23944d29ce43cafd0798564b219990ce766999c695cce505c96266e66eaee4ca76532db89b54c668434cfdace48a71b33924cd6e3f5fd9bee462237dddd2ab912352f4e28b743673323387d971880a7446f5a22414644628bbe48487117ff1f1d91f84f8ff0c275ba23b6e86ea83986475439e24574977e3679dabca5f7fc5a3e6b7f9aba5c268df0c25d2cb5998b8ce86284d1d6195700327a9b35139753dd17808b8be82efd9d59803417dd7d877028519402dcb897e9e5e976b312c57a96cb22bcf04d5e1417e180d9c65a29bdb914efcb9be972292ea8c43a0a6e9726af446f6d5662bd33525e8c1bb3922832d97967f9362b49234bca8b71b39b5e1d115f10d1838bb48ce3d1be57cbc1d8f6349db969b6fc39348c6d33da8f18db66e7d5fa9e425adf93c4d836c3d836b3cdb0be359db98d609c8408db986e6ec6c36889e6da105ee453cf86f0b2b11e42cb1052dcbd2cef10b38d3501cab8e7daac4712000bf79c234011face0870fa46cd195ebaf89ccd4a34d7ccd29c11e02627bd105eb8eb2984d8e2eea210383407097144f370511065eef76a414420089a8b1bebdecd3d79b3d441047103c41820a670206840c0dc1d2fe5dc0f5eb8bb889a3c1e2efaa10b6ef20393c9c20f4b5cdc42469bf018b787b67cd9b2c5dd7d0bcedd7d0bd096d045bcaf0f5ffcbcbb2af8b559fa5084a7183d774b28d1a33e1cd9f9d4e799c9abc3279a6bdf236f6996282f9f423e843d6ce1ee3d74f1598e64ea81a987a1194e5f5c0f2edef4ea6aa636720265c9a3230f6178e0e29edb699cf280a46dfb482623f1924c566e3dd3691e7e87277608c2dd757ad3c1a8697b9c66272fe25d95abcbdfdb1b9764c638a41d8434b9cb3d2f2f29b1decd76b8d1818c0e4be860a4c33700070cc0077717f5c58f6b680f1dbf443389d3bd82138d5e9cfb137a273eb33ee137d18bbd13e39d8b448c6b610051dc457cbbba01c06e5700387d714ff309e263e6032bddb346130016dec56f02d022801eae77b0a7a5d98816a9fda8d4aface5a11c069942ce08c00820000000731200203024128d874432b164d77410f20114000580be627c529c886194e314328c186300000000000000000110006407e2caaf031ba6634736565d9eaf727125db8d6258742d94fbe8064855b1de6f1eac49e287ebc0ca6c829b5b6a97b61b3f1ecacef8b17a64852dfaabaa48c366a4c2fe21d3fadaa2685f737317afe8a1cbe88a9b0f314ea6859c8c17ba8e30cfa2805557720680a991d7fd1c6a13b182f0bb669f5c05860f100d58e95a647cde5f8abe79e65caf443ae83bdfd90aafd8b1b5a3aa47b4ffb34b143468c197626f303e2e5abd3f1b291becce09bc42292109c488bfc5837f366673c3b430c71d2ebb960463819a945409d89987475ca92f306ec17801f328ac89b57f1faf8ee43ea75f59af68fa570515fa190003bf31ee1b62a73cb782dea5c0e3efab7349e692979f1fb3b9ff2d580c19b2c4439614772c4bd95cd6b62714ef657493dcc60ccd7e5f6d9f043f5f4d072624df28798e0c17ac95e146ef98221b551ad98815dc33b8b8ef1d5d292a65df9298a2c8a2a89b7932f344ae3e762aa6fb86f7a1bf8b32029f8ef768305c5e4618c89e70f70785b28ea32b873686a12907a49e54fd1c3a6ab284471e2606a4c530b190c87858baa485ce404af3eadc618e22595c14b1fdd1edd1af04a42f60d93f640b83a5f0a9b923c9e0a631be86639df42d2f201dede038624f3706fb4b36cb271ecf40d46fa74520ac4d7e832f21a78493d91395bf7df274139f4d0af3bb215a27a6148d6544b64d6c519ec5e10d8701dec82b7ad8c2fc0c3720c95a03d3d3a1de82a4d61d9c179ca6ce324ba6dd25d5aec54182cd071d2b807e174ef43e0c5261f56f384efeb3719f9cc1a700182a3ac6032dc2ceba4107c470a7af23621c1eef0625a4fe57643df9dd4ea01932edf83738ea80eb10f1f557c42ff0731c02e6ea01c353bdab132020ac074eaa41fef1cbe4e9dbc72fd3a5979cd94883f4a5ea7255746ae3db5d73ad9a4b699f82e7ab8c40f53b06a54f66c617cfcb501d04eaba3fbbe9a79f4aad5ddd83ad98c5f6a4b68272b7dcdc52e1a9ef6c82d08accd673694c6112b7b5a71f97dcdbb2fe9808a7f6a008ee552eb37f09670c4af6d98d3ce3bcbb0d94b13552dadc715e7e3a93f10c505b8623ae51e82a3f0ba6dd68128e5571d6f9dd6003f9e85cdec46724a31f431c832ae32604d05f83362ec6499aa6403b29bd933ad5005c9eb48688fa3048d338632187a2b91ccd57d271e8a00aa3ed5026c810917062913d99eb4e9fb92477949ea10f520b95071f7ec095a3254deeee3477ea918bdbe020a8236e974ba44b1ef95a1b198d5992a0d7b210639afd09dcfdfcdcc206c6febd22bd7e7d189ecd498e69a40dff71e4d3fa480cc479b52685634e9379ace687ed5385ae738260afbf15dae8e0833120e8636639cb7320820cb75d158e151b8ccbefb284162cbca3a8511c6ddc3f1a77ad087c1e06e6ca3f9168125bb5fd5f34143e3470023ff2a65ffa76f317dd5b8d25038c7023e7cb3da98fb42738e8d16306aa89c40ad71281f1fee8231df702c9e9b81e01a00d736a32088e91407c9520bdef5dbed5965268f5e706dea53c22242b1f88ea19e5a17ef3ba1e35abe9d0d7fd276711595decb6c535c691baea15f0a0542b33309721fad985f90c9607811e7c701446e109ef0228869f80b64d90fb7d092b739ac3ed5921f05d32ee9a94db5a0d34e954785ab7f8dcf06e497b90e6ffd3a4fed53e584d2e39be1397397e45224ea4ee1f3fb97110c791852e688140feaeb759cd9e6e4c9518a6cc313b848c2ea65f68f6a289fe88217ba04a430ed8d1dabc9ac16dc13b44d42bc4eb0187f6d7adf4fd943fb5b04f93d99b7dfee3d68c2a5101bd4e55507fef2f6f767af335bef360e51945caad42a1e11b87eb0b60736fd92c164a09c9f1f7b6935e5c9587bf494b39323e482c122708b02576bd4b27931875bcff50c0a832c1d6a97bdd7b7bf9e8894a82141c2b89dd4de7ef0a9597b2578ede724bd0cc38fc5c44a5e928b9a657d09b6a006c31da424a70959110cf93bc18eb0e76a020980300d8132d6bae1d38bccf8b24cc3909d988804dfeabddddbc04c0e965e33e3b97f52584a04d8b157f0b17612da09d8562a04d67c477b30972c4e38fb99734e7bad051ed1f9b19ab67c3f54ca8771d4925c3c84bb31d602b45c3a2f856227fc4ef766392f4ae2257ee582982114ded8ebb835f16818429f95c0ee25605285a1f66149a1f5a86e8f48ba1245b867eb87c2740d2238cc0489204e594349fd7a23f8500947f39591590087724453ba763bab0159f9dee7602183f3bdcaf0229a6e54099300babba0fb70ccb65c30da9b046296b8f99002c02439307acb842368e79e931a3c0b6f56b88818ae7151fe6383aaeb4a697d281119f4490e05be701d8191e9219412b46349df28cfc8a529dcb14dc9ec85d0333e46533b98c8d280e982bf0ddbcd5c6d94a20983ea58aeccb56bdd6dec1d259a7e6a871070b6ccc9e248d482e1c0c9bd0d7d466695f9a0022806b3b7adb34054cb03a679179cdf481246aa549ed47f4ae2a2a612e4578a2782f96ff401f5f1c538f9cca58affb4a705e43070ef77671f7f4f3e3ccf1cad001424074b780907119f68e5028a061bda5d03a7d77c623593224314b7ee2394259940185a20eac9859edc0161f442f98a0679638f8c1191106f6453358c95f1200c38f313e15f3f4ddb46d36c2d8c8495b06ec95c125bbef2eb3018306f2bbd0085aec18506ce598b7f36f86226debc3ac1a2541c6c64e74553f50da19629100b219f5b591ba95b914b260c8b145edc3cde32c5a520d84ced4d5994990a3e15bc4d9b765ef9625b01814c6f9b1db5865651d3fc602f735cee3cc1ac1650b22dc91a0f315c1937beb9f41f477e12bd96a1a85ee7af8ecbc8f412cdc1705b8892c095ecf0cbe14296a39e78513f06de6a541b251ea8bf702f9aa01bab145351f95782c750cd8a9bc9cab59d36db1007b2ced8d2de39e387524044e558664c5ea1170d6e0026ad1aa61509255ea61fbfeb50f841dd1a82905522e10d985a44cb718bdff56deccf72a3f222688a2b0597010a431c2bdb9b5ec540bb4d510cff9ac9ab331000148601a5e4e609d5033aeb7371bd5c5ed1b3368e499d43acac0444dd9225f011c1ffd0a8b9aaa6b6eafefb66d870ad85cba06d725e72e17d4f21d84a9cd6e38a9b4fa37d6f8b0225c8cbd8772f9dadcd6222dcae3aa0b41f1f0813fde01f6b42d33f3a832b9314c42b0b14da85a1b02309d37cebb3717505443e1349208e5a2610113615cc7f0312cb096c0ff78a7c0d25b9fd649fd90c3c941bc7537caf3cb6f443915fef25c78f4422226666a526fb47378bbf721b405c9f29e4c2d74cfa47bb06d7f01a0d77083ab37db4907ab044296d5f5bc944ec1dabb727c90312eda037657ebf60d6b502b932d656b299e987bc78b5f2f10f089f8733194843ebbdcb2d2cf769158b88deb005e8b6949887302e91573a12a0861a474fb36fa4e1ebd772d361eebe9b7794ec25a52e3b4b3d6a333fc6db4098fc4b4cee99fad061ab46530af220c98877876884a4a66cc1488e454c4ed5ce798fcc3e7f1242d16c232be8dadee82ddc0102ee61e35c6c12f13bc2386bb88ce16c7252638484108e37e36b3cda8b4fbf9748422ee946e52ec2008831b36068d3ee242ec2eb1eeec3d0c93fe5dec409b566763247a5d3dfa34f6ba3366339016232a1ec8d145ec1a8aaf769caf4684a727635c561a5e901a5f4967d40e39be9858c858397b20176771d8e3e18851be0d9be95b0e85a930e0b609dfd1b45272049bb3ae3716067cf8c803ba51c5bd81c4b6ed674add669314844836fb765607718290bdd854331491757dffb54bcdc85c598dbc84f17b0a836ffe3c5d5805312eb2cf0060556b1401e304dd5f44f63bbf036e77f6349cd46ce0cd5a055785a4084b8323e690014ee0dee9933df2772ebf48eb187e47a998c16efa79ae8295f96e3115234073e6987a77527286a02e8db556a3b62cb284973209ffd228ec70be782e88d7e151de25961e3ea21990deed12780c89fa7638aeab257f727c1a7744715b8e257bebdb98b26c5d4c60838a728e62a94d9a98959ee2c77bab6b19a61dc828a149d7e48bc73b408582910dad47f41b428bbea64c06fa79da63384c98f637e5d2182d4f90a289cb1efa06b4cd72e546526255547cff3262d7dc99a6827d574d17421b042a1e0ea5c895845f31f216c062e7407e15d967d4b6570ddb868d5502e5ca0b7b47b38a9b5b1e1dd2d6bbeced2f5718d08fbea9ed1fdf9709048faf583d00757888e76f9619e4117c235f143c598bf41a3a96133358d823ca1cfaadfd29238772a2b68622f8b93c4f6f64ff0f9e7978a3942c73199b66be3fbf5f42b87ecac610e8a13de2b4e62e6db7f3d9be1095637742d649f68bfbf0567eca032c8c04373709b1add181085c07d20f47e313035c43c7d436579d5af57f3a5555456e4c6148050b1a4aac5d25c3a74162eb054e1e0e9913e4348c3b6f21836b0ecd4e2ed8722247f8acd83508453e85dc338ce5d35f8a2d433eb2d4e288ab60b62f291686c047cd9b738857442f3923e92aae754aa1fae01a6980bd31cbf587cab95340211bc49b35649db9a3eccad6138aaf5ca979e5286a89986e4c51de27edffafd6bb623a8ee932c91dbe8289b7f9b5ca23833314ae7c975b04c87622b9ed8fd56220fdbbd03cd2c1d00f95dd4fd3e289a9ae52be864f1eb58816a00df43372c1299cb1fb91ebd940c00417599aab3c3633880e29757c1210b746dd20b8d28104b8b5512f9ff9b0ca5265aa1514823ff3f6dc10306033292dd0c9c73331a2d373c8272c04e8b64d352ccec4075d10b837191950e85a39d20bb28d50f59bab514b50faa5aa18c5854d3c411dfa5559b1a2d15cacf82b38da102479553f69e96bbf8178bf6973cd86b4f4a32a6528577640b1f0f2ea9b568e18257ce5743b3d0d2ca345fef43e78d72623a49e83965d458088c2224d8dcf833d2a453242f476fb9882786e12405f2b6aeb5ae8520889a51b4fa7afc79c0125481519da95b1bdcc9eb3a66164a10deb8d86aff31d7cab170001449301378d33ad786565993973131f592ed9cade581f6d14b9ae7018db3725b0c0423e9fcecb2ae8b8651d669f6e2b3bf30011cd65e2a710a711dc9af4522e8fcd8e168e33d354602ea68e25142343e0b49402617ae20616d2ffeadcc99b5c38f82b880dac2a9a72b0c4387863a3a34d1488451b4bf84fbb2f760fa077bfaa7f3b0638c863ed4016a88c9e28f6f4cebde257439b7518484148f662f45c9a319177cf0ffc4e94168a3f43e49c82074f63a811138b9aad95c32eefe4dbb3e1685b746ac75e145ed14fb98ea7d70a64f22501b6dc817ba70ccc7cfcd15eb2f1fda3f08c0f1307329a0c821d642168b40926d45035453c8067c048fd09cb61a630de9574b798b4f41cc438d5a3a62b76a6e633622e312a49f8f14b88794c6879660d48e4809c4274bd7870c8eceaf596035119be0df4956a77556054e803126babcef28b868a0188aa39b0448b1d836028b859537a75ffc6110baf7177be5cc0418a9068feb50be2d2163d12084ea9c94ef75e6d3f1b2991002d92af44a6404b96e1906e20b2a5c55facb9db4e7f51009caa89a28a58412fa9b232a6069e0d1fecd79ee4f138ef48166704a89c056a007a705533fe6cecbd98a808c1fd371bb020e5c7644da226eef725c611ab783ab29d189cfa66671248b0ff8fe8f053c5c02ff3c48c689ad7893d9e87a5a4c2cf19d189571b03c9da32d0e6274f30925bcfe3688163e28db85fc06bd2677189c643192ef3e51a0c73ede5cb169b5215788119452881271901a10ebf4c4c12ce4a0a62bcd3dde7839ff93bc20eb23bb24645439cf130d399588ad7ec5bc2db5a2ef7ce2e4bddd7262de2477cd58ea50127dd6c2ee8c88374662a75432f1f549fef6912d3de79a11cedf5b48ad878e3ac1490593649438f4ccfd414cc4c1787c34baf134cab0a11c96836ef7be67c5a9b4f4c06aa23ed75125fe5cd506c8225b980d645016aeeb83eeaa84d02fa2bf5559c8386b99045d35cf301d4fd88499450d8dc73d4a440c0ef4500fea80e81064f392253cb6c11a89b3386112e70db68bb15980de23136061392f41d2520c48b5b1d593871f74f5936ba801996a34faf1d276c51b45d17063583e7e60ae0c5bf97b50573d14b93146a31b214ee55fa7514d0919f46440ec8208fdefaea0000d9d3a49069e9508c7390cb6a0a181fe1a2aa519367704a9cfc987fbd25794e04b3cd7a63debde0c4faf1d360348dff6797835cfd269b66bdd4be98fea721df16d11ac9e8bbd7070cee7bea8ef1c60586dad4dd30d91d6e3b5d4bba980987af72b40b971f54e3f1dd9fc6a4af7da6925ffa916d2b6093341a45573b44f8b55dac3c64dad150ffbad45ef8bc7e63bf7df00d7141066785da4e67e4eafb1e15f4fde9319f99c86b70d18188ecbbb587ef51ab5135a221b27e95a4d937a832dd40cdef13426f2e7ffd09be5fad4dc9a8965a624774af96667c183cce84ae9d88a1ed5713226f07e889a52c88c2d8f7d742291d2c80ad74b5de371e74bcaa4b47d7d5120762e620fb466e2c434982935a483c572d7bec392ba73dc4837881a93f7e220cdcfad8630ef84e903f3b2104fe7ec3b494f2e98673401f614211bd094649110e38b2a081f960fd33614e50ef84080d29e07523565ae9bf1b6cb9c0d8de8026ed4f18a812d40550ae685c7e292d7a649dcd4b6bb7bb8c763f7f4d8ad85b4875cfb11662dcf6aed0cce86229e0c538f88a72996cd4828b3145ad7d61130f2e40d4d83bec8b7f8fd4eb796b36c085bab346e3f17b704a5a70a4839d194cfac3ef4eb67cca27592f823beae1398e2bae5d3c70385e1ae208b66b8c93b6c6b03549686b488452bf40c55bfd10447e60e54e404b5d183a9079cf84bf18c7793b270b1507e8d426e0274aba20da3c5596219dfd64b162214b06701a0d44f3a1715360de7fde8e6211fc3febd22984abfbe3b5f2fcbe48282ef62d273c754f9df09359f288874a8c2b788d49093afe8247926952b46de86ce593ee9ed9f6f8bf389bd94f37918f22fbadc8e94e415839d75243138139e77fdc3f6bdca8d1ad589bed6a11049793d7c5863c3a55445c2e59a1732a765cd2c180092c8e903f12cd8237395ad6133036d35bd7039d4be1381f4b59914dfb9da543f0dfa2db4e558cd9e035059c03c9fb746ebeecb6f14b48df879aed2a0e8a5a9e4d07669a9eee3d358d0b68bbe2f8ed91bd03db8fbffe869586f7fc12f2737c7377956ce9c5b7602c8da18d0f01fbac6410f24645d80c791c8939dbc64213f78b1ff2f12db4e41df0367adfceaab5ce22cda3f601fe7516a29027f49937df86cd578d9a5002dc8d5d976704cce1dc22ff55ca1039e518380445d6fd996b8c22636eaa8dc7274fa0901a3651d5eae18b3219c88101d913ab986c823b7e682447a4e7523a02a4e13ed10e068421446faec6bb26aea908a2d40625aa8120858394aa20983a83de38bc8d013577710daef6eb1f6e415118b117dcee0d38495f0bab6671b08ddd7e800d328ce8d272fac236eb168cb89c93177ddccef51edec077f34168178dae2422c2200b890b3d983298b11bcb3071ae02e1f960f075e27c03f9f1ebda58cdf87b7035981363f713a1341ef16091cb06f1f6df5dba3ceba095f2e301f1684cd022831928ff719e2d60d3246f52a5cd0b66a460192a132e7f57ba808990fda86789b1fde6602b393bbfe20219680f5c453248513621da0410613c1fe80aab56a024a1d39869875aa3e52f17fae8b97bfb1e491c1321c5c88abd046908ff8d920182a26fb4d90f414e27228a9c5938dcaf47ee2d3122cfecbdc12beed13c5e6005cdd46ee3f10915d005b38c0289522ef4cf42ec6fe4282f0d71a26d9a6f09e85fdc2954618afa579a3c217f6fb73192b4add011288987c3d778a90c8a96e0c52210964eb31cab192a262a8e9d0618d621885fdc26800e8e31185b724ab3ab0665ab0b2b62dc17e97adb4660ef325a67fe0c4dfa18ed38c353d8e919251140cb62a68a0418d64d5c8846cfcd1916de30013263ea701c9f0325c492c0291e5df55f750c81f57ca159bc1cdf8623d41e0a600754f60d62ad32d7fbd84ec1f749f91e9235b4bcf759d76324991ffeea6648e4113131c5b068ef8e602f53c842ee28912787fb10efe73b673bb26c18464ee47ffc01761aa2757bee8fd218a1918f8994462a5ffa444cf061322f78969f0f606f6aef921f1784ac9ef54621807c76e7976693657d504c89de63e1b10ed3fc0f3054850a58c9024913dfeb29a20d4227e05a8dcef1d6bcbdb6c552fffb27a7e25fcb0c9277103cf03bd0f8a99817a0253772a4e393b0078e81af9241982a40c7647f4b7296f1d117428e247c3df1d394030b548fca5bc51e9909b3a4d3c81618a6f9ac02a9c10cf08b0423f9009f8e3b575e954c9f689a7903e89658d0659fe82cf1b53c78c73a3aabf7b44f1e648aa1c3f71548f77e7eef9ebebfcf68fc9a10471c90cf3df56f55b5c948688b897f0aa8a26d3cbf1f50a7a0b0000a91831e413dc148c74ec0af135b7f845933e0ea00856dfad0b5e1d3fc240bb98000b8ca20539d3ff7be2ed069b660c9117d288e226ec0623b0033212a365f21c4dee2b9e79aa03f4f0ebff6878112770a0fde1173ec2cb6d9fbaf4fdf5c24e52ec1523a1152b643943e268c940e053d0d7ca0c5838425a35c39206f0fd98ee101a5482541a0789a6ed4a82e0d2877ce8500baf95b3e802ae71b69dc4b971a2fa3fe7dbbdbe60e68143110d912928e8a678ebd0f5a76f564ba486d5cacfdc066bb38424db58ce1ad71e2eb9a33a2ca674aaabb903712568722fabc9216c4f22ac1893ab41d617277a969d51f236408990a7eac066fe52824e312c5039dfc19221cf0c3bf549a67d377c502ff38339587693f61ca77eb65103a43dacce003cebce6334e5dd583a2e22c67e15053c54ebcc4c4c8440a2a194f2f8f94afabde8bb23112d7eef08b8b132b8d0191cc5904adbc7057ea1686aede73bfc315d1022092a15aa6ba6566e496e56e464eec2950304cc15c28438d3543160cb505f31c8131aaea642f2664060cf39953a2c08fbc88d0b240aeeb77d464384004320363a9d41c0cb11ae207b3f11c3967cb8f561dfb138d72b31013d0b7fe2489b38ff51e77d17bed0daba5a9015b1bddaf42779f6524fd93e5e10bff95f0fc6df1d5f9ceb5ae88764a439ab97831c841ed67bcc39d626f00bc9fbd207299528a319065c2cff7a2315c2a48174ddc0df9d5b14c2730e9264d9a17b973b93d51545baea7e812e50f1f19d96baf73ebf4888f26f7b389b8ffe523017aa1cbf764f38b19a502f2bd04d008ddb4cf975edacb66f69c137b3a53b09b7a8f2aec954bc10e5a96c207d650e0de6813741409bf8e10fc5e7016bdb04c1d5f71177329b4d262aef03d50038bfb7e8768c85d97b4193f811fa333bffa0b2267a98e6078865efe1ed5fa08bffcc35ecc2e5306ffc9190f6a71787b8aed45e4ab329a648c544aefa8314f3bc86eb961dd9cef48d698eef63e94bc71473d9d8ab67395733d89c93cf9ee52f1bc0334bc040663378c191bb2bbdd1e86c76bf7e91f21aedf7e443e14b5faebd9c63e98171cefcf80e86a869636d4478c1bd185db1e4acdc1140f803252da6bd8bdd7fe3ebd54e279d559ec0d593d0e190d1df3fc56289892272a5f361403e21e91919b7fd3bfa75b5140394f93a130764539b6c5774b45c7df0f767954ad557cf03920c4afc5dc713c4e46cd19ca9de214e63a9fa9e3d11ca8a592e94d58aafd6741fc2ea85d70170de8523f83ff241303ec08fd413eccf47d4021bde6e1db271f3977b07acc4b2214179b3051778313dba9d0817c0896e3909069669f5b2ca38837c6e46fb712cfec4f29758c89abfb144047105d8a0ee2e4cf29f89142f606e56e9759d021fe994306e23467578cec04f824be3496e94bee4fcb61231850902d0cdca51d4b0a62f17e827b935ec1313d0e48ffdd2ea0f18977ba9efd03f79433f386c270857cd1e03df0dc59fcae8045c02a7912a681e2c97b395356626197fd22975e40ccad39f9b486887faf2b2fea55e418612f6729e28859c0dfd9009781a8632fb1ca4eb455724177f4b57e0cd0153dbf8c937918442646c89958da3438420e4cb9173800a442fd94eddbe1e19384eb2ec79fbc5daa6ad0190241909abf94a0b681e513428cadafa9922f2c0d074892984bbcee65258897ecbc3f74d8a0ec315d035ac323fe285eed5c47ae6b54a39f6d8889887e49338158a559f1e58e0acfc86a5946dc6462af203eb482f47149de2882dd94e8374ce2dd53eebb0b6ee58f1f4794efc494b0be94f1b5f380468256eb14d0a41b21693e93d87b7145f32689789b625e1bbe08e1d7cdb19bbe9ad73a4c7f2416163c85490818afa4c0e85cc9a963b79e944d2be1d1b58e9b37f0e3a62344d3393955801a207746dbb38afc63b6de8354591acfb8591b56151ce9c8226fb156deea9c432530ffdbfb22ad542d0aa1fcbc9f799a8786730ad4dc2fee6e30400aabe8c849985b6f4910bb8185cca86a188aa628614c78daa82afed3d99e9d190f72bdf929c3f7e6b2dffd0b5aa9216d317feb98bde8f079441f9745335471b9c06f2f53f371f37fc0374925ad88f63d62267087e364622cf5bc63328a653c7bb88cbe86036640637821771535dc6e962334bfb1efc8c10141e10407feaa98af17e48c288a93065bf3d8d6f54e747e9d1abef8f1ab804eee28fe0bfddc89de8d13f3fbc636f5b4e5f8d8286316d2960d5bed149b3214575ec59e44b70ac2cce4388c85e1143616e21858729af9d66ea88109f0332de6e209a55cc0dfeb912df47eb2499e7e126e544981884c6538d6192aef4eb1854207d0e2a22f9382f966369e4558f0eeae6bcff3bfa992cbe821d3f63db3bbeb6a58ace1bd590a3a927448bb9438aa99786e35c7fcdd9af099b673c76022e39143bf938236b0dac440639e16184979bb676a1db4c99a10cf561aa741ad9958a2608e7dd0296e6219e632125f800313a240c81fa4d5bb1ec400134dffbd39909ce37fdff8791bf3a2567600abe0b81220f6804e067242d28e732abfb650c8c3da28641c8cf0e0cfd2acb7fe1fa1aee8a324c615239923d67692b4c227b717c8046a5836839a8298fd372be6b696664a2981816603b459adb0ab13882c31fc33a19fb208587b8bb34ed9cc8f6e213b102c482528e602ecff81fe1960b6a3bea738cfdf5a8e21a9fdfd204ff981dfb7a0e00416237e4947739455dd41d062ccc39ef54f69d0af45db356087d7eaa7a98ced08726871d1f0220385dfe6f2bc7fb5edc55822c5c24e5aa2c9d44b3c7a3890522af291d7e7493a27528218de70433f30883212f6f9543d3c960bd06720df1ce984bfb06a64b661bab5ff3cb249f8a352c9cd95613fd1eb9ecb0c7c60d20e32c737c779a5954a0f9e02448213c9cccd57b52938880b4f6e9e677a910d8402158832b2638ea27b3d64e63bb8bbde7743091ded6455b95acffdb516ca0532ea04837977a689b93c8d46f14e4a237014f83951c1b117e461c11f87cb2226c597078e6ad2c82bfb0cafb6b3cc2c4ecf0e8deb0b3ac14c3edd43411ac63a5dc9564a9fb66916c9d9297be4dd35452a1f2673895c837347083bb57509f8be23216dd02e5a1a653884ccdcc57aeeaabeaecc6669288ede3e49c2307b5f4f64cfa17c886413f1d62fb103366609b712cc7b1d2708107ce70571c26de126fcfddd3a5de2538d7fb8b7cb3d3749e067222712ea20568848d2f2ee6df0513d2959d65c3167472552be590516969d27b812ebc32628fc074d5caf2f608ea1b1800428bd5067970f6d897ec8902ec296bf6c3d27212c789da199f5982356fc32c0e8704ec55860d2309d0abdc2457f627170837f58e143ac24508685e0997cfb5ee7db2832edcba553b8df27af62fada09893084c330998e693dfb70d7a160a72737a076fd8b0b738b6dbfd2c7a1dc06ec44a6c9fa872382512e713bfd4470dd1341131f092abcddf56452195f466ae4e24861e766589c0482d47cabc6038db39ccc2801b51f851a3355305c4731d1658129cedcde3c8ff916a2c7e68b5af38d097ddba1d3cfde4f1acef85276d684ec6b04c810059af2cb5f9a19181827349c685e5c3e3a7d62d472f5465842387c6a2dd8d672492a2e1c9f5bf708c695fdf1c2f3e14068947077b4c9aed241dea65a66f70123ac5c736c4a72540300bf4fa62e3dc28c3700fc2654b143ea50606b334841df790d1ba3ff5675898317d71f8c87133d03094ee462d6986d8c0c2f2770077914e2f3dd7ad08285f7d42a5cbbd71c72600434d8098348ce15038ec93cb64b9d9f17d08ac2e2877a3203d98ae89678ebe4e145a5dfd01fc9195ea1b69d0be952f5c164283c82d97dedaf40749f9906d48293bbd46b0f7e8523d620dc018ba28828715333a8cdbbb166d54b9668168ebdd5575688732b22d2fab82af4ed4b32630309ad96cd51706f172243daef337e42926763c8b7adddbbc25d31c53d99ebb907b220d9f8ca535527b309b72b2d2b3a9222b9796ca6cc8eaaf3233233a95b1c05e9282e7ef06fa5c106549d40743865ac1bfc25e6d26389e27c3a3db05814a6ffb89a637e8d0a29cd30ee843132a7e6fe17537312cf8c9410ef07211d9a72d979534cb389907b9a3cae80b19c96996f960266d1f0a8fd60238e91c4bc89a29ba5b0da0e5fc7aa1495f63728b4636998e9c4761440561b360ad59b62ac6a8661ea7d139c65c586983e69b6b80c56665e6f1fdd13e494b4275bf9847d5ec244cb4f188847069bbe8997f9aee1950c5457c1f220a46bd6983fd8052e5c96636fb6e13d257c87b8911a595e54baaa1e1e3e8d1263160cc31bd254354e8590db70a7b081260c8c0458348d8a6097a0007a164ade31eeed31974af34eff84e0156c697cf1486dce507f76d371609b7f4ad9cd682e189f35f942501af47626c11786b4053ca45121bdf1a035bd71a69c640517932c3227bdc4a139ce0511f57a3268c80db9928cc724ea41ef2a90bdbd296c12c58bcd706a63fc612aa3977ebb01c8e4ae912e5c71e3009d71014acde4c68d15a8a040947048459cfcb399a4eece80668c212e306accd7ee6eac95bae64989be891f5bc02db207d0200213e8e1c9779c661239a70e48d38bdaa35f1cb87e25f5c71d04061e6dfe8639461a75cb856a659f80275b4f7d44c61820a25ca210c0f0f001aa09010924434dabbb41ae4900c38618f1061b23af3e0b40689764e2a32549ece2109bc82d04e748026214f6dd2bb6dd743b1133f1af2d3eb43ceceb083c9fe3ab1c60c4ef61d4d8df30b30b3a372ea09a91e6f63cd0e2c8a04b7ad2214c41000996abe58bf46e9be7cc737da0f44e8b19a73a0658241341c0176e0f467a5b66647cac0b872ac62c011302152ce7de3d41e7d2908cbbb45d99e4ccd153887f6a1f2f1b044e336521f2ab5dd77a4aa46057d22379038198981ad9bbf5ab420b8f20495a30f3486037d51f003b09688eba8e199e3a3fab572d856caee18da523303f2cefbead8c41bf813cf53b78dc5b9509ffbfdc8813f69a572ab3fee51cf60f4c7eb6ff02ef68697b57cfa601f225286f4a63d9d7180e1d4e42a3911924dc795bd640cd3df41d4c0fb761802287e7e4be599b0a9c54f229308804aefaa5f676b7652b96c67192b585c1ee4e7e3f37a3955c0b1f15772bf80d0cb1e457840eada1054e6a81e793cad3c384bff38bc44e5350dc66df52630ad00e00b0a77f86c2e65fc01b49b1eeaec794667c1967618f3fc54f389481272369cf8fc2627c8f4e4f8765781eba21a02e3231a3d0170613a70046384b71c7e686ad2ddfe2199f4bcf338f2ce293bf0c38b3a026ad90d7a099f89c684b9cd0241fd46a1ba05aff32b9b49cae86d2198c775d2327d9e4672596f52f8dbf1bc63b479c45f8b98bdf94c961039ec4dea2b541897cb839ebd8fa64a7878d60c680fb0cedae9d458d35796aefc62809e379006cc59892be1658c55aeadb1b4eb52443ae63ba474abf8cee13dba2e0c9b4847c37b1d00f53e6fb6591d31f3d5ab3854abf585da9b10789dcbec83d6ea60d93cee23c2379148fd9d02f15e585002b50dd77caf93bb86eacc82cf0186f59215d758be0986a66b8be0f2da35bd5602a136702b06d42d1b0a5e8082001807c341ca961be5fbbee2923688c5066d4f297ec28c6cd82caa4735b1763d3f7ae3df1f8d8de14898e51db45dc3e19e4ffa601aaf270fa670a14ac55309b55723ac2d624b7e21d78a015d4c7f0cce722a3b8fda35bd605ef41855c85c93743e1fcbcb9f7a9c63310c0fee503e03490c6d3ed93b854d2b3b9d8710569ca9d7ce09bf1fbd60485a78f3b39785f5b72dafdc174a444434a3032b326da3d9fbe619a89c3745b258c3bbada45bfdeffffa8ec887788f9c330ae9587b137e2109c4ad40d5c84fd266833b7fb4f8ba94a6fc1ba7f43f95de37afe7f3dda23b1e2f496ab60086dde0d840231a871428afb7d730c3590ab230cc7420f0d78b4c8643206cd6a16860af399f25e9451933b5f5de13a97ce8b9890c4bd6043132d5e8dc1b7ce25ba140ac26ef72247328470f26b5b593058757ec4f8eebea0fbc401acd8567da8cdd58eb653b7e4038a8a2a386427265c36ab9d9f77a4f7d7abf9cdac04e2d49113bce36c605aa03b14ab8c6d5264667106c880043960d2a3ab73d1af6b8d034bcee55fbbcb9a6c29180a19ad883a1f90eff4e9bf76acc1247bc22d8cfa50750b15f4a5f8b1b12ed23aad0821ec5f4aaed07d385d20c04f4e61d22ddb153abd923590cdbf87dc02f86d4f591a4f407d24df8c2643b10067ecfabc7323d7dafba3129dc8df2f7039b65ac368514839351aaae561e85010ac246ee9d9617e87bc839a0d2a4af5368485fdae795251bb148215e85ed3ad659a3cc6f485c3f99a8b8a2c96b249cf61d0d960c45b61bbac47a63c25650362c914459a1c0fab2fe51cb656d1bce901acb6d26fbfbb4b2e340df7c4739b2e0ceef1a2f91e4efc029667e27263300050219715755ca1bbcff112516b50a547ec87773cee98dafc9268be23140c2e064fde056ce2d0c93d1d16e42c17e55dbae6dd5a105b1921e93541fce243a1bb9af4eddb151aef20e81ea0ca2daaad77cd071bd8e054febb640b8b7858c9078a527cd449fe1903fb2023dc6fc7744974d7060d07fb3b4beb9c82a1bfacfbc6485b3de6f585de94fdfffa1666d8abe750c6c035bc073b19663a7f69fded9e8584e5b6d56fb32c235cf93c1b91cdcf02f47fa1a8003bfae69cfb242ec3d52676183eba40b4eaf13624b3c9bbeba42037e84a497f7c9a77df69e3afd59cd9388739bb07c567f828c50875b17a81474a637c1075cc8b16c857e42b52c3a780e30134ade1880117d76b9e2ca7255322c2ecda9c13e54343a2ff516274f2eff11ddfc6f130f24e7017479b55889ab378cef755cee1fcecb0e2861793350eeab61b611b7a84706852a217aabcebfb1fdda6db49a6f74c3753784029763fed634ae5e5822f6bda39589958437f484ec7ac413b4c15e5d9fa5ddd17378acdab5a6767ef9b9f298ec3b798a50276a9b6f05d3d2a39e31d3c90d90ab96d2f1c9fa8a56942bf0ad46ce0a4596fe7ccd8aaa5b0baba0cf52afd00dd0cb856937059f93fa688030fb86b6b72b9c179d15f2761345ee4c656b6b854aee0d90f78044f5cf231729ad9630fbbbd132add9a2c1d41e29dc3989c12161988d388026820521721a3565183a6eb786af617858d83f7a5b667db65d9a3303dce98086273de78a8adf15963917c55ca8d2d2ef39dd706cc4cb6aa913317c33091671bcebf7e0ac55b20491e0ca5f9f8347b90cca84c7a99756ce46de55d5d3416b1ec713c602869e725635901b7889993db076809d5b46a459fc92e696c4daee0a2b6f89c0c8086b9bc1e80c1545cc76a4acf972fe7557ddada4d38a28ca1fc1dbb13abdf2e28b1b5d031f7c7ce8a150dbdb2179fb592970b299355194d219d88341c8b55f5566a728cbf3a064548a4d03032fe31b35f560e4bbfd27d7001bd730015454cea410a4a4bc129428e751994f19db35d5e5eb7d49188677f6b99768e5c53720c391de33a41bb04b28d1740458ecfd948665f40a4739031bf0caaa61ac6e0fa8bbf0c421cfbaa8f78913381b8a9757d7be7907da18c1fdc257aaab0e6478affaf39fddede71f44ec6ac47717ddea8ab52fbd1000685c3a65662dda1bca58c0b7fbcf693c3fedb2023037e148af309450f97ac44ae6602940cc5d75d5e0e814b2f79a90da468aa631c10b2710d2dfb0a456943a5dbd88cd5249c9f4a1eff7201c0456e26f0684a5fa9482c991c8aa4fdc62c7421c00c023d2d5d50b90c38176ceb06667de3f3b62d12f6a2e1548368784cf382bdc50f1cc1008ea764b2eae2638531179f4e23242f229d201e95333fd42e58174a46821d556f02b07a50eb539a89bf7c49b462e9ef4d4372ad1f4441ff886e1306783322333c92d73430450651bd94c49b61577e68d67670fb5e5e24ba40b7358034013e49bb5dfffc1ecf92d58a592fea7e01bf0581d23a767c54ea86a580e6313f7cec95650535ffb085bc47ad54dc0c6c6969f61775d4d892f94ffd9d8c9c65920830c9de8b8da7acfda7999934c636630ab999e947268c84d5c022596b430814d0b4ffec25dc687e581ce8480e472c73769c71f7149650d8c49b90f7fde16f11298678bf312f84bea618382f1d87ba285125de840c6f12b1b774704202d5b8277c314bacead8e2e22de62c2210e868fcb6449feb2b3a4e9d2c2176355fab8145ef01174f97b4cce0a4d3bdc47b580741a9eb74fab22d85558117ef20b16749b18c9ae864cea4eb8445e1ae18c7c2b2cf5b460a178ddf486c1b1db8a17d1473bebd4773dd65fbc471a8f8ae8ee75d224c413f6b057e42f11ea972c431d7dfb010edadfc88498fb40fe5995eeed57c609d24083662786addb062ea88a05d8a4a8ebe4b0320e32e213c55c966eb0cbe6d7fa067ed403c18ab93861f20e19b0368c3ce99b708a92356b55c230638b108338063ee004c55ef3bb1d14edeb291a49d41a486004722e463bc3f7712a2a7e53d0f6403f9d7afde9b39b36e5d848b4a6d9cd9e5a781f62688fe83f78f7188d86af6c110d9dd2534daa51d01fb081cfa4810db22898fcc06c328d60aec9e64603f33b0cf33dcd68eee1fe81678f4edc232e1008617c5fa3a99540a4f310837291d4907c1c3e45e38975c460fd2652a4803c75b31725de415bd72fa0b3e0d7486999dd199e828ae64451d2d8ab673ef8c3399f68f780c4c80c26f1fc1dc31c798133539efecebe49cd305a9e5dc8076d7ce01f7e44a5d39757a0e7d45bdaab9151c61c6a31a23377a1e00e015cb127bcafda349c2995efc735d3e4cb2f475954ded60bd11b61af5f758de7626ea71ff517c32b6067e4304dca4c2358d4d902c47289cf27a9c046e1b10192a3662587cb960f39a7487eeca53b190fc7c0e036a984025511c3d3a1c967018328010903b9a307fec4aa8fb211af79e68dc4f7f1db82e1d8e6a63bea43fe924e0133ec390d24167e409788500264443834c6bc575c964583cc75dc835bc52b5128c1cbac52843e60438e521f6add0605d6ce08971cb67159d7606e6911cb85315c3babb69ec6a8929ef62a0d6117b2e64e98d89b9078298f503f20299d80203634e89687b006378e069626f806ba2f76e49891f421ec6df476e5b7b942a3461e220e7f346c7d2b0788f1708ca601581faa82cceecde70a42a13ab3d946796d89c3d51d09a488323d7311888dd5acd63d3f0dbc791555655e3769c1a36217ad63163f5c6ccdf1cdc1d3424db24c539322b411f275dbe9fbf706ade17626c2736ce4885bf80e031b6cc9a6914349a185c626df827fafa4ed25930d284acd579de2a02e12e84065adf34c6888307b6b9f419bda9b661136292718f482ceb85878a8749ecac0c7bc6761ab346a4b9b6e33a3d7a5ac3018261f7b29e22e44a3c67ef5bf95cc0ce709853857b410195d180b3719682c98e60d135fe4a3a5fdc4660801dae7f1665671eb4890e36e119d857e15735b5614fef0c0d334cb8efd74595132cb528d4e55786beafca5f72e252c1ffca284be399c26106d887cc8689f5b6bf8ebbf4e532db9e5ed09ff253007b85bc424420de0444d6b0b0ef3857825ac61e4efae3760e20de095c5153986d13fc5c73b891287e8dec78412f560dc0afbe65fb2f9fcc782414c23258f93377d91e03321cc870e0e0571ae114e3693a22099c8f93b05b70a6e00320a16cbd473c63b12889d8c037e4589a78990ae9c10fdb7ef0c88e65cd0812408f08fd4b62c2bc951a5bbf072311b8a806ac8b5fab948373f0efc37d1c0cf5d6f07f25c37b7e9541cfe9abf1cb8b34c369eb93a86959e1827a57fb4be26bc35b2ec6b4a8172795c45e71f03580dd90c16876ac49761562c2138684b6953fe8a22a90977901a6fcfd4ad900a8358a75734801b4fa8f38b5505bb703675b7cd979d75b40b273fa0b33e3ed3f567025051ceb9578feb5fc044d8f853722471a479e667633059c34b8ef6bee3a74bc4d2306fe69e57131f2f25935ba6a52f51795ed77aa3006b9eacd7d4323fee62e9d8162c1ec53c3efe517cfa968a6af832f3963f0df43e2935aaf8d906b352264d3e811fde1a9d205afc915d3e08e598edd7d1e7827a13b8738fcda57efdfff9923b65c7b30b647ca094f1bbd8e27047f030be446fdf49741143042d9cd3b09b526c7f1e2cf1c21ec8a2fd66540c7ca804550fd5e3037ded21a4a56cb3f9d67325ff4da37cf551a8f5c6dc81f5bbf53e7d39ef64caa09aa786067da34f276e69517d362614a880f791da1b37e872cf7906efa44262ab31bc638efbc186e02c51307799c23cc73d0ed77e22405c00bff94df884890fbd5648537572315538be45dff9ef6f8672debcb69ffa7e491a5cfd862ccc7809cf181690e88449e570f14544cc3476dcd29346f10b7e5d2e1afe8f1bc448ab23b06ef9e896bf9d1b668351af4d9d1d0400c010a5a3b513b2b208efb16543e4d585212032a062dcdac51fe112ac0b3b0efc52ecd9c52a956105d084da55c1c00fd54472784f30f491a7a8a21ba78109832bd7f692b9e27a9d588c1b3560049efb4c37935d90af980b5315156fa976c6963ebbfb7ba48ef9762f00719865ce8d6a2bff7706bbd34474646dbebdb4a3e3c342e0ad0a17525be0fb3c045ec3e91df48fefbd179baf6c5bb0216b35b66415597af59e92ec821419c882fa4f6faefc57a739dec20228b7681ae9c33cbc4b20c081f483f033edeb35895c9b7e8ff2e7426cc8b3069dfb342c1eb6fb79f052136d8205c9c063729c2f7c01ec921eb9a206d6772c435dc83304cf898f75cbdd700c9ebd66c00dd234c6d109df9c3eeeadebdd12f2e555e22141e337ee1b0568489b3d4a21d31be6e477ee86087b919c8eb38de7ff3c98cfa41e3791cbe2f2be3b25bb1b3c1e4e55d6d02a81c0a40c6b15e3b992e274c7286e4a3434f3aff695aac05a6159418ecc40c149f69a759a0aec7049dad1ed812a3f4f957dfdc6f7285c840489d44a6fbb29eb488eedcec0e6636f0531b33935d9db36c2648eac3487ac3b5e91fea57e63bdb43dc78a71561764a52b6b036d003739b52df8f8e1467cbe6cb2cfa41216fddc107076882056a30b22c01baf96923bcd3525dd9dffc76f3f8a32ea3060d4120faef43b4077d5077297320f4bc1fba7be26453296077ae162972adecb730ebf06088dc2e1adc08d3457d3c65d5f43cc80f1cde8b6b59bb4b8df3198d48b904d857bdd1902047ba9738a287ce9e829e1faee1ad25a17c37ca690b6d4855a53112fd72449599090380b4886aa5faf0a101d14f10e433201acf9cf4f7d2ff5bda94349ac8e6535696973bdcf144b6667106c6484cf87a60fa180035b25db2a0074730576bc00459d1c08e1f9269c5a6a4f8aff32e33992d74449d679597b8309ed101b9fb081bd203512cd3d14ed3cf9cdcc8d8442df69da05579a3458185467ab52f08d8cd8c5b03fae7ba383c0c364c4f6e0006a30f049d049b7e69498394ba018e898715ab048ad0b703ac240c2bc89471509feff161a7b1d31573eef347e09abf83dd8ee79e24737fbcad8cb7f3e193c525b0c59181cef806b9983c756c07c03be6ddbaaff91662ad7c73f077e79d61212815559df61dc9ecd3fab715a1f3905ae01ffcc027d6334a5f1ebf00993f317cbc35f970541fe8114a0c69c6d4cebdcc3019303a76b09e72773e92ba10fd2de90f2be97eadb3fd8450ef0dfdfebc46e614f66158ef77ccfbb72b652f9a549cdd447487ce55b71994d6189d10d01e70676fa6668dedca9f0270a984a7cd2bdcb6b1c0c973d7f697dfbc024bb88fe5f0a9bdf4153f33e873691e05ff71f2f5efca4bf4990c1e44fa4dfc9a7a9da667ebad287dcb7fb8529fc2320d5f256ef82a60132c0dc11ea955266f7ef14d1d1fc092b487cd5903f530f2dbeed647ca58f623577469dc3ad04fe0d97c13049b21c6cf13c335c04567bc4cf6031834a929b9fd79fafd9caf5ff6bb4de4fb289d4bd95f45a46d4dbc97c47eddb74ae9783806155655a8fd0fd10e48c73183c809b2f73cc4e07c7ea2d710420033aa3b7cfcccc85fe34b5cb97a8dcedfb5f39978a85ceb141d2d6c5ef2efb7b69af6d472a36c0edaf1cfe316c91140d0620bcf74c7afc3309ceef49847d6d45c1b7f9bbdb6e1ed815be4a3b05750bdaf6f14b73e91bb1863ff10c5bfc5f58bce1ee3366bdd0715cba579cd67067a110e33a15f92ae4bafe43a191cf966be57c6efe3c604aa238c602dcfc4e3b4003b952d7c3e4db66af665e8759f4b185223fd87db7e627efcd2643899a5cebdd36e0cd6c5f4d70cf73eed4bccf25ee19d92f4d1f02eddb57ed1dace6f96115e054529d4c63eb3e0c7aa2df6845306f332a9da2ebd0ab7d0a85bf7b9ffdd7f0072a644b6f979130d8f5789ca0414cfa596861deeda3bf3baa24f1dc7d5752281d72bf623bcd328ddd8154d9c3ba5edfc820f6f4f60b2b8c8cf443932ac7c192d7bc694d59973c5bec309ba973ed3e5cef0f267bcec195f7666973cf34bcff4d267ba9cce3c782cd0aff46f73cce8278e95fccb98cd99fac24c29c038231e5f6262fb25b7d1bc3c19294bc694911925c99c5232a5349928478694272365c9983232a32499534aa69426135a3a5c2abe9f710ded51ea199be1037ac78063c89f89d63873623cdc85c45d39d2f819c550bf7c838288fa5ed1d26572df9132893bd673eaa2a848eb2d4e06931bca4ef2da9afaed68e67dc89687b66f4c154705325491bd273afb2b4944a3e9b0e5ad13bdffdcf503689f7fa91ff86bd61ae0f8c14d086214163f93090b8b276911538adb030fb4d214ebb1522eaa4816c39ab92280ea4be52d7a4744cbebcdc961eda166837c51addfc3845f359dba7692109ec7716feff17af0dc9037679f3cca7f01fcd43fc8794ac0a0d32e9e8017d8002893398cf317a52018556689177d0ec48c9d2f12a6b3a0965659f936fbadcf5f901e59f6de52c44a09a46448aa37f80f2d13031017056791b93326a0ff0505e8a5c4f7618b6276f8e251a1d90561c69900ff1a9339987e47d9dded209a5c40998bb42a431f626e149d0f690d1499736bbe6d3a719ba486e7e87d00a8da453652477f7a8b86fe72587ea6635a09bb074e696ccd552780ccf312e5628b400010e1e6ccc3193a11ac7b4d8cc8b18f17cf2ea53f85d078a412ae8cc6e78a3540cfa25f3121bc2461da57d3fc875dff6f78de755117d210ea18e3469bd0840ec70cded065b78fa47b53aa3c8426294ae505174a362a03753a0293d1f96eee57c85fbd4fe80e52c1bdb689409aed5cd2df04ed9eb1718d671b1caff6369a3f28c4b606874004e7fdb50c1664c90e0e84eec20a548743a7fee038b8fb04ff182208e8b61e0406f220eb2f1fe39c0be4a0b970b3cf0480b6ed82f76ab6d34ef88572744d8c618d4a6f8883a09864b096ef7512e21b2e8d420fa3952fb46369ac1fe8645927b8c0ed8129b636ca70df76787e5f8e93c96913ebc67b6c826c6ab9dc90ef3782613b2285e180195510412af084a47abf12c8274551780f7446974c4c9c0d4905b067c2728328d82a37cccf6413a301ca2e9bc0482ef97c02fcb9031ded25eca9d51d1cf91c88851ce49a1201cafa1130ab12e3cc1c1dc4f176a51b729643dd1967156242e628c731f8d6f0950a0af093435f6ca6b0b3018bf68e82bf3a19d89bb66f41ee1578bc8f1c3d8cf967a57e904d1bc7984a52fa7136aa54f3297006b3b9ecc19c4ef12c68fffbac81c024aba7066f38ce07efe6cbe076353d93ce1bf7c05cda5a998f1483f22523c1a1e0fc9a894c68f00a5b7b5ba1be9c9c7be001aed206edfd2999af30f1c49f5ca47594370800ce382d1cd065d7d36c2878cc2dd5c83519b46dfb149a105254352cd45e8b2862777bc536197cae151d19931fef239965a5e65e347325eed2616f8117323612af702e2403062fd65cc45435968afa1e349c2b72e16c657472f0bd79f47f375b8beb0792b6a6ba811ce1c1a10ef64513220e27e95cd98a8d785f28aca6db745dfbb1b0799caae79e4ffdae5a24715f5054cc21a72c102d44f2ea770eb1a21086a2e501e00b3ca71e3617b62c5ca0f96fe97cfc7b18bf255ce36d93f6c6562c37bd9d8c60f1e590e274bde581f0585304a035d19d744f63f83c6ec4411403dfa7453c6e10fd72fbf7801b29f58e7e4131063ab1f19874d3383c3d374b3dad4a0a735040a024e7fca586b56ad7d4e39711cda9ee89944366d82c8b6d5f33e497fe474146a2c90da8c95af0c5f88c5db5b96d725b4ab02a461e6613d48e260089d0d8a3b0857ebf706f71f5cc4361d8ee0974800fba6b76e78ed8f039f5746c0a0f47792f9d021424007a19f0b34f84d14bf8a0a724109496ca546b742e10c6ec72fc8abd32f0b76abe682bf46ba7870afe2aab1c090b260a1c0a20a0a4c0b27f0fffb190c2832eb30aaa53a7bc1d6f4c82de8ca53d0817e0ba77e80672d22859a7a8924876d1f1a0c0a4dae120fa435ac8bb0c4fa81f9dc5484704c433c7a76171a65e8622b6fd626a3ac56cd63dde855707c5d04e784d9c24e5ac4ffc39f10486299a6c10ebc28926cfa3a0ed06ba52bb7e8746d27086c9f21b3e7932ea18321ae0c531e7c5738786d0106faa29e1a836e4a285c69854510fc85c2eff36be03b6e9ff3cb9b28e0eb16ce077e50ad265e6dda249b3de252ba15c839e57d44f4f04ebb2264e80454543d08160dfa1f3a9031e0c85b6e3ee3eed77c809aca7d65992e51ec3e427ad12c310f33c3971763b5261d1ba127d03a0c26fe2e9c060f176329776d96a3b3a8f2f7f3bd329ca77afd75caa2a0b56df74f2ae2cc1d291157ca6e255fa3053b5b5256e708a8179126035bb68fb8ec5ec347b9daa121f9062711c0fd5138a4e7871139d6100bdacf7828329b2e56b99be197c86f229e164014749c323a6f072f6e9f00561f895bc7ae80eb9e670207b4fa5289b3e17747df29aec0ef610c2e1b915a57440742cdd2445c5178366483056165e9f1adf2c7c9780f45e8e7bedd9314ff886ffc994ac44532130cb087374d48f353170ebdb2af188661a3fd8904e7f25d799479b04475b327ccbac646c72941625986155f064bd5fec4ad59c1602a6cb459a1de1cbec2162ff8400deb4aa7cd0f715194ae4f253bf36a9186d383cdf13b9be78040e0088e33f669d625cbcc1716acc6ef2a60acae6d623616b9d89962a4c1e2ad44165ef2f627cd818bc95092529faabeb5338cac761b7aec4c574ff205881b10fe9587eb6ad5346a93f3cba8a947f43321e9a2fee451a67658fe53890cc33bc534f001554c307716a0f58bf251afb0610d1de56d3dc9063392acd3fbcd02793c516cac3e55a1449c689c7f8a4df75d67ce7009a5c47a9fc9904149f4fb06d00f97ad35d1d9f3746ff0a7b53e1aa856d90eadffea5d55fdc20c607182ffac7128f9152f55c6bb76cc60a0203b347344b3d700b7dc0a27e170b77e09db32d5bfc09207b5e126ab26a7d50f23293a1013c1414013005e5310497c51c30bec27be1f2219d9c54b8444622eab2e3751960ba59efa329fa78a1413c2900fe2a4bc0c9fd33d3ab872c8f20590808d25c75a6a0d85a15c498926fa4712a98bd304cf5baae436237d35ea97ee099965db01f6f18af59d44537bcb0222fd6bb538c30d8d73533d02efb25daf8e3faed00b90f948cfd3788389f1fcfa38c819c010e414ae87231709fc4e462762aecaf8506e2d5b43c984a013f3979076b3a9f3ce7f0875da62b9dbda339f0938e60a7b0d3159c19dfcf5ee17e02ff0dac1c17991655e856e649281a1deeede86d2c3c679a42fc4e7d3c8a7def669787f0e872e219a64b49c6c4c05101c48aa8e53e9608e8bfe8d126db45a5bef2a25ba97cf1657467aeaed3554e9d70ddaeb9222182ce475c49a5dd940d0ece9d5044cd843907f56c02ffe993318c9ff92bba948a88dcb0bac0c7fa0ef757dc67b63d13f4004de08345e78ef13ebd895c84f59033dc05d12e21c885d147eaff538b6b71ab77713e0a24885316ea0ee672a2f94db1683908c9081f6c5058989daf0ed8cf22df1f1e6815c6e58f1a671339a46a52ef24571d2d75c098a60c4767cddc1988d8a60e1f3a1a8df49106511b71d34ec44aeee2510cea68ff8a5b612c4ee00e01077dbe7e64c663a1a53dfd8cb8ee99f462c5824a8d1d59a4b049042124feddd4dc83cf884392d085af94602ca8ff95b313c2ed03488dd9ace860beb6e5a9171ad6fe27b457a27ca18fdc367b9e63c4e6977cb4c7c62912a10c9654587ab35bc2184cbbefd3dae8db46e7beec5465c6ae1adb4cf8e0040eeb1a8e22af6a68ad216470533115fb37833004ab7c47ddf7091ace7964471b3ccdd87308acac50b20479709e16ac5a9aef8784137f80481d5b76512e531439bbdf14202ce24e70e833de396596375ec630e51a6db212248ac01af6c209b0a2603b7574ac10470ba3ae42676ddc86adee6de4ea85428e83a4be28f5afbb68caae068508e5b434abf2d381fdb02d85a680a44e3d638b3dddce36be318394ef910f1ebfa7db53e142b288fb2eab16703b22126cb0c696418bcb48e546c2d2b15877aef80442fb892977a69f995f77483daa071f9a1af0e801f4b95f158da219ec513c845a023a6a557c4d492cbd53ba0751f3c44b4bb29e74c6282c8d55e767e04e72336de674cb55330ec72612717dfe43721c0db05c0dbf0e29b8f01c18519d6d4b2a64cae89879c41071ce1ed7864b38ba1b7b05b0d06f555f85f8b02458ba44ae33bae106abdf3a652c681973c2772a50f9a319059966ed275d31dffcce48c438aa8c1b9350ccec3333c7cf518eb3b66f8b46f2280a334ba74e1b7776e8dd53ae18c7ee4c48352d3780bfe45c733e49b4266d36d3a11595462e4c12e3d1859c83d6fb8883527b9251ce0443a5a2db4bdd68c8d79c8ca5c1dee879e4706e05318ce3955dc5e20fcff1b784add7eba3ca0aaa2b8a79c34ddb836a145cb244f0adf1b910a0c759c6ea2b8c2beda2e62e927216857f74d6a193f0028fa4f978526d6ca24c3e39ac7dc7c1887487249ee5b8c104a089e4d8118ba283c15179a04ce7aa6cfaf54f574a3c1d9218e85cfee774c054ca6d0defaee075070bfa863d79f470282e716c0c9c54a40a235f5e94510b4e4205910275905c95a2750f9ebfcbb24c6590164cbaa58049705c010695d1ddc6157f9d4432ef44cc348dc3f472b43cde94617484d6a3ccb8974d94636ce3b4468e59170e418db7dcbcff5b5b0695461cb1845147968faa49013ccca7d3b519e525fb00721ba7214f10465649a7aaeb4a2d6e0a897d880d870674d8ba7503390ec2ce91b9c39f3e36ece79de2945e7d760f9c5d4caa010a47e083700f921ee2af2f3617e1e995aac64f76e6b9ad55f97604b4c2083da1305732f13d09fbab4521b85363d6d69508e3fc9ba7382a65e9a566f28f2a6ecf510266255570849d15eb4a392d4368e0a4d5042be00287fa7a744efa3471748d4cfe8e1e826efcfc80343e3790defec4017832518aafc198a8a2139e733a0354aae63bb7eacb54dac962b7ae0d513a7e619868d7806f5d96a05765a4650acfc538d0ab6a30f239520d37a6c5b8348b63334bc0e4eba81650dff0b56c8047469edbb36e304bba7b267000b0188f14b782b3d97ac00ea0186c2710bf742a2efc32c0c981fa97900120c5e392faed2366c5f1726088a214e38354b79a5f35568db81e861054cd66144d14d7a612fd0ca32786c8e8cb79d6f62a53a5bde209cd3f912070d786258b81410a1c04574f1b9abeb0fe68c1f38cdd34d50ee682a6f0865dd3916f11ce15111293d5842623b0af41e915a857ae7704b44e6edb300b318f49ebf0e8d0bd5c021f0c7531ae05850660cde6fad0090540ebe023264cd6ad47889a345a01274e216b6eb09bc211de2526dc20d0d33956510787365a88dcccfe876203ff8040fe81ce86552d751beb6260ad5257500a716ad0bb09bda58437c1f9e31b443a435e210f2747e533a4dbc8613c26dc4324c235041f2f45327b99096002bfb354b762e2bd139d010d4508a4f93d7ccddda3514176990cf238ae1c5cd426967e3d7231152e7ca1815971a3d8c7bb3e0b6828c3e6db1d2749eb06828162c0a08f799519f2e0ea7b2e4304cf2204b243abe3a38db3fd085b2357f069843b28a14f30f3883d8c7d6c2aea57abea3bd3ae884f7d5ce2a694114893f3fae44038e5462cd33ec98faf653aa36ead81e076889e2a7e29d4de4667a71f240ce338180768a3c3928256d6ab026d2aa5a94cf4b53f62c87666c412690c730d2f7628a0b20d95b707af009dcbe4080ed81b7ac07112ba067a78e4dc477c98e631721b503cdae9745c128b240c87a8dacd7d4f6269facd7c49deaa732a2feae42e66db8b35acb6a9c9903f90d9977d5d90652381a7eac5159808e71cc6f3dfe5e7fa29d31d297d8868edbc6c4478148c519a862816e85cbcc56fe79f9e260be215c8c021727b5dcb165a60b422cc579af49ddd7589d0c1381617a2b7ef242f800ae518c55a748201975c1207d4be72e3768d088109ba469af207f7ce1bd21c53d93a692184addd9fe91eb2021e5240ff6455313d2881436407c0ac723c7fb7753ba2f395d1cfb9b79479124c345fda1d82fa19f12c08d7903f3a857bd316dc149bc1e6a6033f70a69b969b88f0bd2d8cc1d7a14541e05afffa973181871cfd28a09111e27e985978332cb1edd1ed1a5a0ada114dff0b5822cb971061119f98fe4ec5d84e97e93f0ea6022f2308f9dfe0fa12f38101f14d07b9ab6fe4ea7770d88b527aded7873e02e59f7633c662f26ac78bb4445275c4e52c2cb61f5fd917ed066c91ec959076c06f6544e3dc410fdbe7d45bc581164ad3596efd9c90b12ca7280b280963a11c381f7f09ff994b438df59b1cac088afdad482cc73b5f5b70aea394593a4cce9370334114b475b73583dd3d10224b7b997adc4aff2b7ff9f54da974d11699258b6029ca28f0ce023bf5a9c2a10aa11601dbdaa54c8ffa200411c0ad0560ca7adf145d6fc81d4eb71574dbd905657d1c5287b52612c2bae40c2a63dd484254386b1ec8036ad5b7731b15d05ffb4d6df151f504542bf504ae5ed7dc8642dcfa8ffa04f523846b312f5ba0c913716a31675c913049c3fb3518bab1aaf0bd5f0037611c286a87e2edd221d48cc7273b47b7eda2c6a593c89cf654d7f05416d0c9b9caa953ab30f0ad7c7d63f6375e8b837088e0b3d4c1145d417f112d7249e6026bfd61a5205f5489deebee5e982b8f1e8a76a561a5ac9237f2106d96da116fa104aa47e706459e7dac6d578a32df3fa250b8d54c74ab019453bdca8b8dff693912e06a5b9e9f5d156a104b1da89c80321328b1818c08cd9a14a25cdd8ce4397621069b0baeee4f16b5c42c9c6248c05d945ce8682c9ad74572215943985726b06b40ed6448b3b2c5913d0f9518a1c138f3af53fe55d136da4b22d001233edffb95a37f6c3549f4730be9ed1f49627c350202450b5b422d2d677d3eeb3accadc2e5042901954e6a81a1fd5ab38fd27f0429aa57d42c24ba08905df8d2f144c299507da7c31fd0d84dd63200e0ec69289e4d32b08fc12bf4789f10a66ca3d87f518ec8e096a62dfce5a9dac39c0c07c601d1f3f28a80309a7293517be3a9216ce073010e198ed0816641bb62dc19ed8fd028fd69b131598cd5b91e86be333bf74d73ecd627dc7746b6042e40236252384723024b384c23e243385223a2cf26ee64d262ef97eb7b64b83f49eb7d686255d92891436bf258c23ac31403e3cce61c04c9d27159de8951003d8f9afcc5f36a8d248b2a3aac68dbdc087ef64680e13a8dc41933d79fff4e71f67e232cb942397d23a782b2da229e110c388f9f9c429f01f0c998c99f3dd0604d5bce0bfdeca11149f49e099b2522fa589c1b7c0f2c42d1f6409c0a545770ab53f64f9e7b487cd74ef435ebf9960f489146b6d40840d2a313a630c6c8bc00ae8e36ee8b104a0d52421a77b734af66d6a9ef5520c29fb66c16921d929141371bbd266b90fcf7ba69d20027e659b5699d91a66f512b58181f8034a9187224d85fe578ca9741dba413e33b289c523ed3666ddd3c886b8c92251532a6d3118903c4c8544326a2db7d768c7c8483e99c24952592269a494cd651b552b438b50a9aa0877347da965258623198dd48ef9e3275647dd55271ca4f54c74ffb620af5d6c86ff1bd3b1672da4930cde99c52b80f5b993e2681b64dca5d5de618fbbe4a5d96f61918b612dba8f76a24682e54f606d6cb04b692da83e477af15d0d5aadbbaa8f5377729292c0cfabf4f030e08149d81d5d3dafd4401dcbf3433451ce2a56cddaf8911ce30fb43c366f1a7056dbe7f79d452edad244fb51a0be90ab9b6a05f7ac3e332a3fc7babc48f983dbba005fa3b8120eb1ad08c0c2e1e8df077939fbbbfe175275b37b70d8e2791a7147725ade70d2e13688ab44a8ab321060a2aae7712dc1656d05094ecd4b81d26a12862470b4f4f349cbf927084b2e7f4943087b8481281f95d2983f1e9c16613bb1bb65a692016414d3e5b75adc228a35c6010f84661ac9f3e941f1cecc620cc52652a4b9520510b32d77f30895688d27d1ca33a5c05085eb727555dc1458dc1601e819d44e8106c7cea2f824bac1c918be08ad060af31bae4c91f9a138ca08f56d572739f16a3653ee219d1cc8fa183e257ff537c9ec7f6a40ac49af701f521601004e4c4af668f1daa0431972b1dc9eda601d57b45665ab786ad428b18f41b612c15e2ec7f4296c24a154e64e1c7e79fc6f684289e36619ba664bc1bfe1508c95b79fd558d561f2d78731be863dc385364dcff4861d9f6689d16f4e131684e6178f1df6d9d258ea4d73c93470ea871d6beae631b6446aa49814cbe76521707c2759faaf2548b1b279a4810ba4618b2014034268a19d4345a2929d52159644170af35d4364a0d144e85a7dd35c28646ecd5a2d6dc2908a2e58380678f88c67563053a016556123be69728bbf16b166b2730ab6257fca41a479afbe76fee8cf50e61f72f8d2219d0a6ef062bcd55ffe07e5f83b59e725f661e16c33009c884dcdf30c628073e2f2e8e8af6a3cae6ae2c099fa7a9d89cf5ec99719782dc6b0a8a4490727b95a18688813886735ddf1233ea82bee467965533e34745b85dc0eef7bbc888adcf93fb2bafcd9e196b7dfcc5f7d34c593f5b97691a40d2413b672f2c446ff468be24831ad9c5235d96328047f825b50fdd1ee6e847d1853acfead050b8d6a0fa776caa8ac47edf5240ba00c4997ad4de94d58e0b08a99b10fe2e7f6489d997f919bc3db689aa60e3fab1c16a9b15698b0ed9359fc9f054db65aed01fbd449d780a19bb014b0b2023d3c7272e278c0eaa00689df3f382c13717640991dd5a7a866aa11d408f93782088cbffc9ee1a077c431c87e4546407fbad82774412b75b1e72d8572b6d4f4805f95d6c6431089c24c4fa8f22fca2b59b05af85877764ce994ea05674b84eb057cb47313800de70be7f59efd46e7b6d6e96f960703c6edfb5367ea4f78fd827521adc6593df997f7258cdc3dbd8af922debbb3985d90d8c8eaf36a2b1a8f5d2f1ba3b35d8d3299955389fbeda64599deb43ad5eac8874793d787786f1398a01dd8feeb3f5010143b6106b2630ed707b2f2c3f4859ad8a2632f2966f16d17457436e1da542857907b6d1a1bbb6a456c6372d39363803fcc093b66d15559fe13fc6938e33c8dae1450c26465a90dbe05ace8f94f5ba454901c97ced60558a0a7001acad3e6f487499138234b414ddc1845f5f3f08a0e7b195653e8e1f7882a19c137ccc5788b46504142c9f20edeab3e6580021858120c50147579822556a432bb7794fbb250456856ab668fcd477d33cce7642f393bdc73d5c9a3fdc88bb460feac433778c816b7232e11c1af111a5b4f7cb4f1bf322e6a9baadc34f0144b2bd1cf6200e1c6934d586bd8e1ab56ee8ddf22f82e49849d9bc0a9ac0d53f6a15ee0fdc149eeb1f050327a694f1fa2e1dfdf84ed950b1c54071d512032a9c78a9e7bcde4c4cc0f5ef731a7c34283e61c2594131bfef0ae03f68ca211b35bae01faee7eafeef2a5c303589daf9b5972e4c22640a9f992524b04393084f4b5d19127289e1ca25ae45a5725c029a2a9c8d610d8a943193f5bc4d70b0350f790a02f935d28eb7eba1d961b53a86fce7e4cdc4ba27d2bad64595f4171848d57f012fb4688bb1b27a971a487ae8ffbed70d22c8754609a8ae14a016264a8793deeadc0cee9878ed067b82f3d9c77f867f2c78a18dba947573cb3e4c76d0ce794c755c6bb7611c54734382ba1c3d8b383f3a6baace6d2b3d3ef308d0e211d8ea551f84836eb5b080c134edc6e3d9c0483eb6d20d2e0aefc255c86d581eeba295916d5710fba55511de560ec3c56b91ddb808b305eb23ddb630a6e56a22bfb128d6cbea21b87929a60b5721b661798c8b5646b45949cc9756457a1bbfc84e377e4d99fc4de9721e804676b24e50f7f1fd2b6e62657407a44a6f99211c7f0832a23d65dd5b4eabcc7d76f75ea8dd57981cd9ec15e43209d71379d126e831c372628ff5fe518ff633d94f5b76c2e8f1ff51b4ab7a2a4bec118eff4ed13e5ebfa76eb8a85233f855737fc17ceff98b2a771705ec80dc619fe22eb0fe079db268b5307074d61a61a2d585df7289dcac9d914c56fd69085615ada4b7950fa8b095effbb0b7b7c54d5d2ca3b296474115cfd4ab6edcc15bc5804f24e6df2d477833c6170d5968abde4ba6f110b7e6413e3ac103da08b2b4b37c3e0913acc2aade2107067c18b74c540b4b68c57feeb7cc21ffca23a86d1a884d69f4a5e7ad79803a3b8241e400bd649f525046e1060817cea20b01e02c97ea1a70394e7323eedd947ca579e48592358b09e4f6d4d032006eccec5b0573287e1d93f6a6a2c16c05b4390e5f89381c51b589f369b5ece87c631342d1028017357862ce7eef8330afecd0dde8c8b72336ede30133ce192ea9ad6af2fa5d1f7994302cff9145f9b73977cd4398f5147d646e73d3bdbf9af82d30ace0b338fb19b4cbc13112865af520d9f7efd4f4fd4e78fa9cdc127ff84ecc529e4051c6c8e04f446c34d771877d41d2c9c0571e301d6c98b402e207d585e339ab8b7a3526676b7a36856d0a1308ba945a17501d06f909a5a246afa874826c3d4798e5f34efc7ecdb6a69e71914abd1901127bababe9af558b7fda969011c9433c70830d75e5adaa37cd0090d09a0e02d12e2b00231b0d137617dd2cdb963ba024b36e28da7056c6cae001448271cfc6efa310d75116b50049a892804460b9bf5ececa73ac055dbd271b3e52b7f51dd510a08c8b37e410ec0d56d4843381019ec396e6bd241da0739d889400bcfda8b9a5147e0e617ff2ab32779cd0c850d4ab051c7cd804ca046ecfc78ae87e5b85b88ce4e589229c68c735d2adf097b717c03bf1b56f62b5951fe5dc8620d63dc597c636c9ed25c96815aa2170d580962792d8a1a9406b6f96998ecbd9a0e2ee3681bae3c6dc98dd03826cab043f10007d9ae100bc9ae368295e18cb50945fb1161d14bb4d4ff45a80324a0b2a8b219bbdd00f0dcb76e391a8290c7faa5ccdc3a0e0efceeef827daa1fc2b3ddb284e10ca8329c1f46fd989dc621873b9baa0a1a692ed42654a6f92bd9b14c3c58a2d56e3276541ad1540120afc4879902a44958d81eba7bde56d2ed0259cd44019c3459b23da76b2190a1ca506f6771b68b7228e07880fcf41f942aa8348e0a052789c5e08c9cae225ed6d224e8bd356a8e75a2eccd5f46d0024f795524229d943928c0ba7e177558b062498e07cfd9435fd7b77d7d1b1a5a74ebd3b8d85a01cec24f49b3433efdd48a526efc7b8bf3750649c40782593cfa1acce07926cf49ffcf48a4578e01985b3b190fdf0930e10c8a35ec995fb282b4f42ac674cb5d8a9a1b175222a1e295fbdd5da6b127a83b7057e7466141435027826514af24740898036cd3dd97a6bffa051f636267c07c455533849b08b34f1fcdf4216f2cfec3c01011d478c76ed1848acf25d2c448ba5fcac4de1d7b2b3015ff8e1deaafec6496daca7adbc206d7073afcdd6308f095f52f4458abfc48982abd6bb4d18185c48cb173141a08672fdc6818b0b5995eb3dc3f79bfe106a6e422f2dd43b36b65173c3caec29fbc357e4927ec2d3fd47b0b74edfcb49003fd3308c3d1fef8bfd13f57ade3a5589349f8ebd804f7c7ffd95ec1c4a6cad03f75008d1f4016a9b84caa6c448f3e38e345cffc1df3bc4cf82b42a7a3e5dafa8d87b46b8fabe0a6e07c4bf9018e65feea4aad9a33a67f80659ee6ff91719ac67a6bdff0f19f4f56df17cc274aafd9ff117f83ef457d50ba63fe84619f7c9f5de71977fd7dfa6593764e18033478384e658cdddf8f24225b9ff5a4da8e8dff46b07fc50edc965bb823a0140f32b0330fed57372f9f890fd8f46139dc5639cffcdf505ad4c07282d93ba9df1735c23c209a7c274fb8dd7dc5989cff62cb4ebcebf66904e8cdbf4338f8e85b7dbc4dc1fa486cbcaf0596362e33e7e7226f1eb792176fb97b38ed66090789cb5094b907fa3e7547c41dd3e31504a32a3f2af1f6e1e3fae9869ae51b2a6a1e8a6a9f3035aad11bb378ace4e3ee1635a64b22875c808f7c2ff04021820d4f55136e3032d57abac1759eb9654930edab9b031997f0727ec4e1e22bc089196641b4e3412151d3cc868976eaf997f9aba219792063d8c412e87b373db63170a1e05303092032c89e3738ef62983f6a57544907cf9a4d3a9fbc259b88b8313675e9f55bf88b8a2fa90a6192bd998024cd3aba2fe3526a7173b75e9718db3d759572f532b4b373a3f5cbf24ed0fe1ee1bb2ba51a0b88161d759de596419257bf5dccceb321edf23e95e9a8f299818ac907edefcf2b9b1b10b7862d3662f45e2bd6cccb6a33c344faae5e3104c59e7c6261070080cdba013e8db27a706a05abb493996e0229d8a4f5a61ac374b388b11d68d9cf078204eb363b7bd5f4827c5cd83316ac2986369fe0a88a33db5a43b050f16ccfc2d2be790880004f582a389e8e63d0d5dfdf4e327967bb6f1199d5c450d5c6576d21cb9b5cb5467b9d30b77adcad6683e69177933377961f4c470ce2859d3abef737683fa7c2459845dfb403d49ffc2d2398e466a4aa215795cec02761b71ed9a670032b6891633dfae92f74aa70dd2431ea809000440b284fa98b1a6ae2740012781dd33e691257e6ea5b02a9554d9fedcf73c65578de8266e0e88f189b259908f0597280991dc239879c88b2a7b1a199bd34e539a62857c36014e65004f0d64f328594f58707b6314073c991f82dad19fecbaf471d159d65aa7a667ee996675321bf3d16be98e6b2b0e7a3c15a045f142c2fffd3e8625c9e8c17a6b2ad40466aff5082453ec74fa399fa3fb17d6a95cb90ff50e85b038264002d7419c9412295e228a4703e2bfcef5ba5a675154408ae198da0f561af7f7bbf215d4786adbb4cf2b8ec579d143f3eefb7f3f3a5c7b6ecf82b28ea9edbdbd9f8cfcea66f9554657d1529392d85b0511413d8da13e42958ebfa7772f991caa7311d3c993634ce2ecd9dee35f89ebc6f623a401f7b451748bb703407955e4a3dfae2e4e0aa5415bf6d7eb69359a541b8bc268d233a7fe512047c1ce6de6ef54efe5a2344e13df91429666acf02f2e3853921a172c354cc3fadce9b4aab453d6b70ef34514c51ca74c4c5100a1616755f2747cd34545df5c8c74b9caa5af87e81fd591b488f559605adda23b4f354743b8378e6435e143e51d491adfc7680104e4dd792a6c638a1f51d4001f010745e36f8f5a785ba6b36b29a33a6d32187e2f8b225c80a979ec0d2c281916d115b270619d0dfe35076188cb8ea841b199b7764d87bd69109cbe9ebdccad4c6170332a95e34970e9acf1e29dea6fffba3ec0645a3b866df1170be3bb303a267e19fdffcee35480ff6f109a58917dbd598b0c99d1ecb935250a84c40808d70269e924f79b06b34eb57930f535864e8a358aec458db04f9df7dcf0fefc58bb0aac700decc2f4da6dea340d83ea6e234935425226045403d2ac54c85a174cb1b91bdf9c76213e1d291c274b9a06c43c2d3ec32ed856f22502a5b3eef5f0d24af4f407169abc50f50a78da68bc473e4811bb50d064b0da00f8171c37fffab641eb5ae79faa5f8986854e6fbd2efbb56df570befafa3f8c024dfefb26b870473dd36b7e11abac16b132c5fcfad69f54b57e1583616c6d67e2f7171d606fae64b687d526677339fa49ac198c22eae667d96b59fcb09fbe34aec55e8ff5c378a24c22f497c09978680bb98a10b5639057c48e4deced7063205baba066e11f88f0c5b50f40d3cfc05b70d8d896061b81a62bcfde8f64877c1c912b39bf0c4e21c60f33058990ddee52cb4b1a91bc3689a59a110015f11e8c515af2a4400ac02ea68ef25afa15459d869d8a74df2eb4463dff5b54650426790e11916b8e60522b87ecee03a9e773df92e1f2e34e6231296d7ce386e2f9b0bd1f7aecb659272d640bc25995842eb51a36c0949779d1cc604dc3bc36b5bc54bd3ad97cf13dad09fb61c8fded303c37091e99f554cfb2d107f8e33fa0f2614e8ff4c665b83fc59dc7552b5900e57a69d098b6954ec5bbc8e6861bf33c68d31bd2fd60066a4fc2bdc7819615aa6aed73c1b5ddba870aa77d9b48c6528694304c0db7595828c421c71f27a4689759c0d483fede24a6e4098ee3bc5405fae91bbc97eafddb4c898f9f629d581bab017a34c37ecbda14739c5abad7696c1c66a0d2c2dbc1c62610d0acd868eb80182c4096f431096fc1181f8e8628b1c9f1990a4897a5a24a316730bebdc0d3be93b9dc0aba4c9fe1375d84339527bc6e7786ed2697a6029a10a4f0d6d7224ebc73bd8e88284b7de287bafff6edb4afd013e8340bf502452f958e53fb255df63facfb021908b5fbb1deb1e23600ad116e2716291ab9534fa80b71ac088cac8d464ae837d76e1b2bf7b36449f7bf2b551e10d7341bbdec080c7d1e2e2ef23d2a38d3cbb954422983a8e4601e663e991f37db3cf3e751050c11af2b3e5217907c7f3e0cb919575aa2035c9161ff541438029560b7b4570d3cece0393395cd599e48d37a06e9c6a269350dbbc1ec9012e2de84c87b43affb2014498deb07da02a48f77031dd7233e7fd896f48866d68d864162de8527e7ff9ee7c290b00a678a6e2c0642591f2f1fa2e365e410e86a4d5e69e8315df70c8d388a59c767aa25b69b5d8e19c64b741491e4f23e2bb8a050a4c3e9330b274aa34b376d1b2da884b8489499df08296a4182a6ff02ec877ec6f2af6ddcad755df8aa8e4c9c5c9bc1cd06f44642c8cb501ffab8c20e878b5a7f560d13f132693b2570ca1433391b578606f4dd5e0030122aeef186859ca5791f8c7b5a5a83c10258d309f969c3a07d526dce822969133c100b524a03f9fd48cf850cfe94b4a8a148dd298a0f8866ecdc65fabe4c66b734ad71568472406f391ca439bd4e7919525359fca3c1da3474ace85fb8f41551bb8d0efcd09c08d2bfa87548868c16da4762d872d3c49454254ce9f98b09ec4f4174c310484252403dc739a6d892647f1337b5d58434e5abaae4df3f0db60614766c3b299c5a2d9155986629a2239976bb67e5c635924fcf599670a80d1ea147ce1dadd41cb2367b52c3820b16d3190d57eb132e501d4220d3f31082adda399eec2bcb9cc843f8297f2a5dfe0a2038000c092ad97835ec2ccf0d1a6fb3be41cd522c3369131e95ce6cc2b8fbf9da08dd7b65a5a4820aca36f3361ee825b7fa89e85ab767e51ce811ad340b7722a186a31ccc6091411073d4165d94278179fe1fde8f9aee95ed28a4b0c42d63127cd7ddab72d80b554d08587028633477a53df14441ef3c2cd2ec8870f8b804ffb4070c057662c45f5e7ae16e656db34b889b0846bb036bc100bd70d5f45d6c2fa325deb839c3bab9952a0a7bf175aec3b1c16997df2295d884a42625f1ca1b1c01338aa48c42353181ceabd9e2411236b56849e86c029dba9d7d75f1a0f6888e277d3c882da0e10bb2ef9fd6dcfd9f7e047767d27f477fc1bd5b60d396fd74a566368f694b5ca1b34707cdd47cea10d308b386c3574938133904d5c3e4d7f9e20afd00ed5b9eed4f219c9b3247b795c1ccce936a67b7fabc91715e7ccf0826fb85a0a52e94843d608623d7df4ea9077f5c13102919f2d765a7bd808061c03808b1b51dd8ddbafe2980ac826bd141ff063c70c5164a5798f2046a7ec2146751255af8b5d506dadfb5aa1587188c0b907a14c91e3956031f7c620a23c5c2ce025895328a0cea5751ffa9d7648d98720c0df574830a533a01b1c590811e0db5c0b7ea77f4fd44b699bf7e76c04d96ad1135dea01bab177d233bd0c3857aaba95e00192b867ff054cd9e62ab997823a0841fd9d69d067a710d362ed20d98ef634ee39ce7a57c406b053d1e0ecddf97ae452c0781cf2b673a264300eb93deb956eff30d9a9a27a2282cdb3783a8ba5f0764b51717cad5463f6bc7906b1d7fe88446c4f01d043fd722c2d9b35b0bac942b53d38fb86e367b8aadec1cab6e38d8b9d9870b54e58885238b17f4c15e16ee2a39c85566c4cca61011df4bce2e472c8d911918601013cfba2baa1456e6bb3009cf5bdc1e6fbc010f6977a1546246fec9cb86daf49a4c9e27228616894edbf49720044dbf031cd1209c1529cbf65fd17ac4a545e2a84f536905a9c3c94b899e59db7bd64318fefb6ba36e580fa6e6273e00c6db83704ca64e742a444a4a6ae89003239310d7e33f8b168e191224ccdad34566d24c10c5b1f4569f98ff836ed667249ce12627a3312cf18b7ea47c35318e1bb27c655bbb4501fc699252213840672aa53aeae15fd04f3216eb6278e7762ee21edaadd8285a0a50e6b5ea48edc55eb2b7fc2694e53e54537d46c3f9387a3d3ea64b3af12a04c9e746653f56d40330f0e14de211ba892174bdfac5661d45d5dcb455a5deed1a35cd7101f1ba9f9aa2c23fefb99848b165e51175246536a29bce56c34b98d38d64f98d9fc7359cbc61c8aa97574f384a762dbc0448269a29064652b00a0d017dd444d1a584684c64897ed5adbe88409614869bfb00d3511d7bea49e7be27d3b93b93bfddd3ac83e6d6d10bc5bda0a080d1bada78ff932206e73840e98a554be66a9ea1513c080e8d3512a9c958d410348d4308c33bd7787b3708dc1016382d3f0cc85137e84c57c1fffa93b156d06b3eb3fb1cdbf97a54d537e41164c011cda65e4464d803666f071800e62a58d4941a2eea85c196194f4f9ca69e8c72f3d84b11f96f0019903433d0c552b6aa035a3ec79c1fc82273b7cb419fdcd96d63c055c7f4e71c48374a552bcbef5da795ea1203289b1d1731c212f4c94f976df98d3ae0f83202c682dc53b114679308163a584b4a57daeaf15bcff21cabbb9183381202182dfbc07168dec49905bc2a683a6b583d002157a3d5ffca3814d84b8baffad0c77123af596b50a2408a7a91275807bd96ebcf6774e2105d9bf226abdee11468110fab3c17ce37eccd08f984b184c1b52b3306dedffbbd144968ea57cd7a8473daf0dc967ec1d4b61f8f2f08c4865116ea41f60abacb6526ed25f5637f11e223e28f77165d370077efae6720b3cb9f0b86fb7c52f33de0ecc9627f4336ab87bfe524363e4900bc4021b30f4633c5f546d740d07f04524a42c8d5138bcd3222208b6e8349af4966b427ce46b82e9718201b914ca19623d11a7bb26b7c0fccd92e0df2046faa4e54429870c36673d8a0f8d6a8fafca0b4cef67ac79b467b8e229c8e5fedea2ff120d447c0717000bc92628a80a8c5e7275c92daf615562d2c304f5193ddb079de14e3d61e02f6ecaad1f367b6a745567361992757927cea022e1f39e4cb8de711a7ade97603758606d99e20d382be754643ffd90ebafaa1430f8dead3da5f49e4ec7ccc36fa98d0aaa7106ea6882f25da0a2ccb04be5845f9114c1028b2b66d0b0b82aee8298920b2e149aa59abfc4b2a156081412a808f2ce90711d12881759be07e5cd9edf5a1298a856016d019169b918d64365ad9ed956deb53a70baf57f16225fa81dfa6bd124f1581105eeaa5d96461a3b139e1ff3757b1f228aad860e49f876cc4c1c60430d528822e3646363c36b0bc8a953f2f046243c3526c34a9640d0cfc7f142c85c252247ca0c777d6105a03cd1a40582a8e323ad31a4c7856a347e685d0fc7f94ff2d6f52638d18a2b8178e2b0c6990d5a981e5ffc5af08fc5c5043843412790c1c313144a54194864f1a5dd24099d228e151d898a95c99826158134d40c1c8dfa071c7ffe8698fe532e2d050a247d411fa3c9697432034ba3c181627e25d5c20101affff62ce30434ca9543e5f7ac2fc7063549c169d9f2f406154aa1409263c07fd4d6738f9174356d6e08b016f85110230636b2889925358190c3da28f9b51862065cc21862855194960a2985d62880ab91786b9339511011992907188898c399e7b29d11bca402632942083e7d5d302c72fafc0a854580ae8111c3ee791ee731ed9f2398fec7cce23a8cf79e4032626333a6f3223df6426843799b1e04d651e79539942de54c68d379581c09bca30f1a6323bbca98cf8a632416f2ad3e54d655a785399ec4d653a78539926399f63c1071b38d920c78bd29bbae0c09bba18e44d1aa0dea481096fd28082373991e44d4e1c79931341dee4e4026f7272c79b9cb441c1298782085cf0395a5cf2395a28f2395af8f1395ad8f1395abcf1395aa0f1395a64f1395a38f1395a04f1395a187d8e16429fa34599cfd1620b064d4e181ca2e6730e11fa9c43cc7cce215e3ee7902b9f7308fd9c4340f89c4330f81c432af91c4310f91c43f0f81c43d8c0608393063fac79d30f3bbce987d79b7e88e14d3ffc30d90090097c0e20567c0e2007f81c4086f81c4076f81c408c3e0790fd3980bcf03980f07c0e20f6730031e17300e1e07300c9f91c406e3ea78d249fd38691cf6973c8e7b4f1e373dadcf1396d2af0396d28f0396db4f89c36547c4e1b273ea78d119fd36687cf69a3e673dab44e135c9041930922980003092c68928797cfc9a3cae7e4917d4e1e247c4e1e177c0e1e8d7c0e1e7a7c0e1e6cfebf820a32780fa214e04d5180785314ef4d51bebc290a7e5314faa62829bc294a086f8a32c19ba058f226288fbc098a1f6f825281374121e34d50b87813942ade04e5006f8282c49ba0ecf0262836bc090a7f1394306f82e2c29ba0b4f0262815030f20c860021305165cc04193a7400449c018fa92134dfc9798f82f19a05480ff1201fe4b4afc9706b066cd1a01ac4962cd1a12046b48376bd6207184116bd6ac5983d3d2462423d614b1660d116bd60c214429883552a4f0ff6c6c5032438717405354f02207832ea8e8e962f5637411001eb890c0ffe73163f9d1c963c6c262fd135c70f0ff2512b6e0e349502ce026842d58da034c59b490cd498bf2cfee31e2fbcb3fc60557f602f44fba02841a4fe4a1211070f2ff432cd7d0ff8f1f27da1e3886e701288c5005cf03387840045840828506b06883051d58bcf1ff5aec5f8f4e0e5f3a3a2d5d66c7061bfe4b35fc97685073f45f322afa2f11558104066998f87f521539fc7f1e8b68d16302456a4cf2ffa5ceb3023622c80010f9ff1b33ae1f2bf070728923ff8f4385140e009341186ceeff935810000c5f9008e20062fc3f6988444688124250c58aff2f75e1582064881486f0c0ffdf6870c41c1b58c20062d4f1ff4c9a204024a43b5296b4f0ffa5412049a2064a9610e4fe3f8e13411130b17a43863ffe9fc42d702394628f31f4f87fd21ddd54034d1133f43105e505151434e6f10562fbf3d2048941306052bca03c725498201e823c288e42792e1d162bf3a0c697eb0be23c4bd0cb35be7807fbe7a178c73c97ce6a140247958af37e14b246a397e7d2e1fa43f115102aa8831fd7d2bf1e9e1f950a15821d0b0acae14b8777de6acce16bcca210c8ca181fb39431045722dfb925965002167a2fa129620f189daf07431d812eac078c0e90eacb1ad5595c1c3d8c8b9e988b44504ad098312b56b0202fb33e50b3c491e5bdba2962c678ebb58538cb43857cec4242192808f5650f95c5a020f10b5fe21766ce0241d71048c48dc69087a3ce211f47d62b4bc91f180c150a81ac2d7a524496977b0862e3cb85ed2ff31d82475fc63e509441a54205056179ece1d8feb2377e3908e3c13c974e0e5f3aa81cbe32068a22d7fcc37ac0e860aa116489286ccc528e58397ce9609e2b63413b431a64ed0489a8cf1b8fc24c8412b94b07f35c3a64bea03c57c6f8500816a942b0f70e6228cf9551dc753406002bcaa178c487502eae8b0e4f184cfcc1b020dcc7575087ca1e11b6b908ba8c3876c487b898b5609b73d186ff2ff36bde28fb274170030987920e3faffb2fe556fe488afc9782545f8a214d098dc879e744212fc1c0caad2f1771224fe8bf74c64c99fd79432532a531623ee41e698212d03fc92b85f929bdb0bdecfac4648fa5a33d17c8c3f00d7a1139cf32f2309cd5653ea4c1310459590c3991f789093367f18c9abb8cb8f0b543b53174cc1ca34aa58e008fd8a52e3825174a5c2088c364c4201ff2d1d3311961d4a627024b8c90e0ffa340492208531157fcf311fcc214d14311658a98520407ff44443208115efc7fe0474407449886a0840e91c310744c4b97090a6ae932e3104f5c3c74094189108208c185144f881ade24041493101f8422538248230809fc3fd6d265b8ce13e36abcd7886553104888477c2808feffa6204cff8f0a0016b47bb8eb08e888bb1034048aaa0f0cc28a46171fd29c08e31bebe0111f125318377f792bde61429fc73dedb5c00fd45c047d8eb80bfb070efe1f13fabe091ff8f8f7810bd187285ee41e2a101f7048d85c8df78394b634f9ef210794d0e7e53087afdc03971ea0fc4b898287438282c090ab113d226f877c8b39f020c1835282422e94bfec05f419834442498bce63879772e400a403174f42a603133a3c31e9e02487397ce51c8ec8219126c1811231448d469c892b982b6b5511ffe2d0f97951f92f8d70431a429f97e5150261297da5cc248918ca30c9bcee61ac8f0914d4f8f1228cb7c030184aec5ee24a7f39dc61d280d38d08388f1cf12115092511fe4b21348900ccd19737f88de28bcb7f0984ffd207a50e2ab9a28635461c2be250d4f079accf1b038689864750472cad83e697f072385261652f1c31be02b1aef50a359631916ff05381e3e7a97600435c8d61c63811d885600fdfa0cf0e16ee8540d8fe722ad5b55e21cf02585eee41e3aba51ac5cce24429f022354ec41015825cc0d17394c33fabebbcafc4c17f69035225a5398ea0fc632934de6484e68fbe9c41d65187f1f0681cf3e6afa0546af3afe80bdf429a93296950094901598abab080f0769ec449654ae0a10bdbe06ae42deca893a274818948ff7f2a95fa2f39f92f65f0ff3827473800c88de14daf25264a4a949ca239d11732c2f13c3c2987332202fec7a0ff0ff45c99afb094d846ac800c543c96e26abc3060c410158e1132f1a8cd31ee02b5887d5e18cd89544399170d654e06cb360c75a319d22899fe4b39ff250cfe4b17fc972cf82f5550a2e0bf34c17f49829c5293ff52041fc57f8949024c333879fd7151e42a1247853904bbe849914002092498e0cb3b186ae44763e8ed306320a6930168084afcb80bf4728805015a84e18502c02e628812b304137c20f68147d8a7930a527da0a7c61b7bf441f20191e7e166792bbd88a9d5c2bfc88536efa104818b8fe250efa00b042265613d8112b10fdc397ce94861759ef871918be04a63e10b3483a1c430bb5c19db2aa8207a612eca18dfc15022d683611fe87a6141fc158e4118383a000b9cffd2cd7fa9f45f225df24fb2845409181e161f1fd2b9033fee612b2b564ebcb712571470f1854c424afcbf5adde65e38a600c5ca3f8992ff274d72a58bb1f906351fc12f73e5516c796357a2affe7bf61f093862bc0c2cb53f2ef21eb896c752a118a2547b0d38625fa2e1028698f423623851d9946e9320b979a4044738d008238b6c40032445d4b0424ae48c09c041c793e630e5b124812943792c51744a7ca80492562530ffefc29b547b3c2605f4e951150003853c1014443ae49f64c83fa9907f5206fe49849004f92705426af34ffae39fe407898f7f1206fe4917f82759e09fb4c73f498f7f521eff243cfe4977fc93ecf827d5f14fa2e39f34c73f498e7f521cffa40afc93e0f827bdf14f72e39fd4c63f89cd3f898d7fd21a6a04bd4e8328869c47759e95290d1762885a21cd12ff2b31af324fe3c1eee95fcf50980fdc29e23e45dc676f9d22ee8326041836f00f6482410af17b8554ae6046b9853a0ac2c48fafc8e4cd8dbc2c186ad43c0cd30879fd13396b7b9b87de9198bb60a823d651e7891e91117fb582443108438951a41c8d50c4cd3f30e42e4c8aa7c59efd813b2d30ccf2338a5e1710680c7908f6e8803e23e8237a2c8e8c1b6390c9e27310e79d4ad018822e23ee6dde6a794141118c89448231628c63f6bfe85da9f279ff0fc19bc6741963c2ff9b808d19c35a5c14731891877beba85e9b7f625c99af5262aa8891422a91d03883740314462a0534c90b200069518561644413a2f9d074341d1a34684a40d3848426282614987ea8783144855cfc29c34d3f26fc7c806152542fe0f0c2eb05d60b31bc40e60530270ac59fe89a3f51d7ffe770f0272d41fc494b0c7fd202e54f5a487fca52c89fb2b0f1a72c0ff85396a23f6519f3a72c3a01f814b0abc19a67d2410459bcf1c5430009203742a62f81ec8f8f222f9ac25bde880a3a21019f80277541a4232a2d44f9ff346ff281e3ffb144655346100ca7702f1c4d5eca90e2e89f948028ba6cd145897fac0a86a1386b7baa0fc442b07fe922f4433a7718d8bf9e1f2c9c67193f1ee667e71da3ec0535a6090a8201fb3cbe7b07a71ce52130cb32090503573c186a7c8147b9c5f272178f58ac57cb133bd10b4f196fc450da6be5ef6883622e1abb29990affc0b0d25a6d11180a6162eb45064305056141435f82beecb1f26b06b0a807e31dd6418cb358b9254ae153424e048e01c1607ccc20eb28bbf290c87b10f7c5ffb35819e31895ed4ddcf8ff42e2176ecb202bf1e5a292c2973516365894f8f0d5312a54b00861014385c57a752c188f2122cf17cf595dfec4f0acd9fc7b75206bc7c4b3850a4fceffdf98b95d7e0709b07fde4e99ff29d8e7a9014d3b50fe9f0b6557d7a1e45f47902741c7c80b4f190c3bea44209d2eff2d6d840214a14fd5d2462dc0d10204501f8861a83c06041372220c1c370b04bd2211e345230ff3e7612b9d3526664ce444210f83b1e0e4ff15b9c2e60a15586a7c81578ab862f45256d046a9d407b65aa056895614792cd502c32c56f6f8dd63650a2cd5257b63c41015f2d1957756f52a94fc5771234c98918baa546af32a413f6915edb1149137661f51470c51262a397c6a144314e889aa14074ca93dbca1908baf90bb5e61449d1d15be84d4f8ff190b41cc0353aa051418284ca97f2c0d306d69a0b6d779e1145b0f2308864bfc7f00deb46d79226f85cd82476d2e8a2c56e651a144168b44a1f87fa3b1c5f5e31c51324cd7e89f85201282b0cf13fac0b008b37bd83922306252b0540b0cbbca64856c8f65520f0157452317c5f1059aea1462880ac110fc3c95eacb3a95d224d116d1f6c8ab222ca58da16d2101a259f02f1ef1214c62a31157e5f0954df38b69b230a4b911d090e6ab3104bdb8725864924f3c9672e5b0884b1f5ecc6044ceb37c628e429697b300897c280371f1e7681fedccc3e8aca0c60a0378717f5c44fda0421063e50e6469c1509b8719431d7d52b8909873c8a560c15045e03734e4b1781186ea20d6e21a6cb158bc08133326450c5f212862e20bf4c24cd4258b5fefc1501dc4505158acec617273972b875ee86517162548c4886c9d1877017b94f8813c23e8c345315fcf9006bb8cdccb08fa889c670933823ee290068bc02ee3ebcb0e03fae49dbcd323f21f9ea3d1d5e238abf37a46d0070b6775f91393657f3c8c989636823281090ace8b7cf3a1c5a4c00576421714b4a4b06a6923d5feb26a84143040e1122c25625254e07e151fc10f851754e03e4110d30916309d3087e98433c2309dc0c53f8649c18cb63785f38e4991826152b051e41b93824d390127abb2d85e498c0f282e822c8d198d227f09612e1064617c359291600294886163ce22c10428f1e32e570bd439ccad95fe78f7758c95bd6c264d0f456097b12312100b849207ff8e90a43c89e7494cfefbf877c384f17f120f3b80a3e7040a2860196685046ce462ef200417051450c0ac5cc1f6c71f278cc7c1020788c7e1c1d9791c1d1ccd84311182e20a62889a321af1548a7fde4ae7968a8f997be188618ff1d11343d4f74aa58cb211163002128fa58282466e1a61fc1b9c26a7196efee4fac09f5c88fcc935c84bc0a494c53f298b2a388c7c487be33fe9061b4835889c771ad41cfd938cf43fa9e89f44348a7cff935edd3f69fc2789e10c2ef06bb1866420793851a0fc7f0affa54b40d8ffef71d3078afcf30f14327d50c6a78ed4e42e1f24f1c118a5a5cbf031eb0fe4c146f083827d802595dadcf4c11321af4b6dee81121eb014f9ff29260fd6782cf582170c1c37a7260f380f3af87f51486c6152bc74e0c4ff83b9957744a02351e442194c185197f9e142190cf7050c07833cea881524a652e20b3c3a62714f88b3c4fdf110ec513c28ac3c6e7e24e5f3c4537a69f5983971f9e2ff73c857d05e78e232e64f5c7afec465fb1317d49fb840f9131710fec425e74f5c70fed413c99f7a0cf9ff08722ad89c05fa6c16b8e5e36a8a4016df39627d5fc470ff3738b0f20d3736bc08fad4b0f3cecdeb8f7217d0e767f45a5e97ff1b1ad4fccd11ceff8dd14dd1ff67716b2e0c07823f5cf4c93baed710287ab90562afee25f2a2eeed2c9b7f45d925f2a3ccc258798b61651e868760b6ce8967007fe2f9f9138fe9ff37782a8fc206db69e7037fdae1e24f3b477fda39f3a79dec4f3b13fc4927913fe9dcf1271d2cfea433fe4907e84fb48c3fd12ffe4421f0ff1748d013e668f4c688017dc29d7776de11f98fc8adc6d0dbd9e1ac3c86e1e16b7c6519bdef25e489a84dc5c35062c6baccca2b23be1a43afc332f67558142898c8793ffa7afe6f5e23962f3c3e9ceae7cbcf0fa7e2ba88c1f2c58baa8c8e97546acc42d90586ae2e4457843210988f0f8140acd706bde02b2d3ef60881acddc33f4fe8e358a0e149ff4fa21f5271a50c31441d752a51ccae1b305f3e7f5ecb0bb5e86db125e6a2f00aebffa60a95ff312e882a561e731f02454c4a1ef3165de2e6ac3ce62cff24bafd67c1f27f83efbf6805849dc5ad792ac5c3df1e38e64b95321e85429daa80b9c926dd3b048b443183598dd9c8ebbcf5126fa648f91f732b04b3e02a86d97907572ee69d9d77be54860aa522ffffa3a050282818f7734a653cfcff349ae5cef4845161e1cef474d1e9f9e1c2a8b81f31403a3e5a7ac2685b9affbf4101735846901546b3afff9b139e8c202bcc8d42e572e5546a93b5ce504ca5542e4bc73f9ef2f5e564bd0e4fa9122b1f11583955ed9ff5ff2788214a5485dc55a1fc3faa62ff8fa71413459322fe7f4ba5302c043b5544abe1b532ff27ed8457b5c050e51275ce1ced88f1b07c3ae14b6bf0a60412fe5961c86990c5d2c1fe6f46b811e112096e1840fad295120a243c9e24c71ba4351290812086a89d79f8a7d30aa087e615f6d737762f520738704820243fa688719a8280294884f0a729abff67b15e1f96e2aea3710a9629a939458a142950a64830e5a328d27ab1408d61fcf33e50ccad1668c457426018be44fe81ac28fdf3a6ac10002c04319fec838da010148c8fdee7754ce423c8878ef850942c32cb5e68a6645996bdd040c158a0d097c3a0126c4823252815a4b90c1d887d39ccae2c5fb821fc88f241390ab32acc2bcde2963099208a22a72848dc847003c2ffcd07ff371edc748003207038e243226ca2c0e10a1a46a872c54871206243881b26201105879dd40bdf0e160003070b54f860109c2390b8410f2041409823bcc00d5254f1854b0b30047243ab0f17aaf801dcdc7007a9b2e65f4fdc700342251b131ec8c28638946083260a115d6c304011932c48b032810d5a00436578b004151b5600734547a291486a90c482315f888fb051431a1358b1061a2ae01a906812c4901c401fa8c14c0376b2700044076a78e2431c0850c3444503234314683109ee42031858fee021004564a1c1879b0c9482b052070d60e448a4a696c0000d1e648023c10366625043880f381f48d182831a2daeadc104165743c3104204612c40355924a092c3143244a83145414917e20ad7111f1a1b1314a9f172c40024a0a0220036568e420bbaac404514f2a84a2019e030d21d8e22f860070e341d2019d5514375718941c8e8000358b34515d91446420e48450172e40ba3a9013624e49c214991253d2d688520004d119b141368287243164503e89c3c31c644a14875033465121e5c4550e4201a628527ce10396281004841840d1a109161c61f32a8c96110052144c806d8f8ac400414468432515c218208dbb9018b2e96e4f142e4a692409cb8d1c88b0b392cf1296a52c62b0706700a04d0127a7151c01d7f808100052f0dbef4818348469511900ab498675ca2472bb4e880081d0640c0f8aa230e31a31a0e469dd70c7d74808d939182454028001906fc20e65146102b8e7e70c404b4f1a20436c63944561363e084403a8836906c0e18882a20967c8a106b50511056800b0d897c405a112ef1b51184128f041176644490009e0edc114eb12474028a46c2cce0014bce00400745f031031a53f3438d1e82cc6084176054928499a119ca086005a9e364cc0c2650718219df87c9b588083aa12971886b025150d0008d2a805c3d60896079620d8f0b8c0762e6b06193c3d5011a62e4c0060d0ee0204f14a0022d00c00166610337c3939315504dd7a40b263c2801721fa454187a8208cc0944890d886183940f033824d8f081fcc737c513852c31890e387c3370f9f9831221263e2a558840a784477c4c48d0e20c21cea8a045071746542a71b85a4f0001200c01089281567780156dec7c49a435491893724234815549c9042422f9e2b2d8d00e69610d49d660090009309ac06cd0c14ac3451a348ca86007560a68dc6029b052871c29bd1103b85518a2800c7e548203932a86d688c141cea13086c21419e0112863b2a1114e30e3070630d24506431221611f8a9840862d16d1a6dc053491010742b02d66988832f4f4d0811d6a868e90c189f7801d36632af0da000db18a024b1e5e15d81f165420d5883716c2811e7a7467bc16be6873c5153e10f0263043480d165cb458e121e4c32463e0716695801110d0598265cb6a88bb712b4959b0aa893491e286127aac486bec20d3a4280321382e10a191063c4944a8003344e08c22210421941f3102022670b943480a1836100201961f7a0726590211190e80fb19dd1f1b08230b497a11325c32884a87233a993570781181382ae825d8914724475060856f60040a9cb9c128f32f3ae01a48490ce13c68e921e7113614e13e7bd8e103243ae11c60f1da83e7089e2dc8181e68632bb01b020530d3c56833661f714e7eb22d8c3696cb333a72c61a3b474af540941514da026dc694d969c112bd802d8cb2382134b5eb88010c41c00733740a871f8e1029e4d0104c2a2e22864cd1cd014918d522580f9d1325887009217f47c7b92084891ede019d4cd3839a391e883c091b3b3cb08556415e23c523e5083479e424d8d43158b0c128a3118340bd31c923198533ee3801a4048d200e54a21ecc2c510a1aa3043229288d1c2008089f2b5040a4a045d0cf14638680e2e791a010f020c3bd21010da80ae9e38f2109548da8b6d8809045525ba4a0ba8108057090269150b5c5b5c3d12062c0a0caa0888a02530008c4f0c716310028840c0e3138804915461338c111c3088198294118c823861670b03cf1528315314810458b4920f994e68e1cc8e02e3c78a589a25f3125cd03d248238395365b0830482b8d668917267e802193e6df70d5e1827884c68d3f3c40e4f403031a02e0b0250c1d0cd48026cf280810474f8626003d682508428517183a7083c6102f302b3098f104973069646009188898c233012a131001c318219e5062052a0a80a1842636f094a16487338a5ca083008629083823010d3eb8353cb9e1cc0e455b24c9429867bc0cd100483a60f3e5cc0652289286932858301388216378557400879907e8ac8001d111589831d24e982292b011333c681049640a1433cc5c3089c9688b4e2db3079b0bac1185a6421905d81108ce214a1865c009788ffc8e1a65b64c02125f87cae078e142a469162133c79528909ca105116498a80483450e60481b32db0d11850d349003992c01907c91c1933ee6034f8861e68b9495316a70c9b4aca1842563926882021475e4bc31068616acc0c183cf69cc09705862a5ee718198465c6fec0064c08298300ab1c20e2311dc10f3c3cf227c04712085981730e00800b6c862881810062082105fcc200265800f0e3e491e410314811e2c4e50020d1b806cd879801440408102d2b21aa28badcb094027302448c3864b84307d74e0d5edb1850ed38048d21882059a2f61c4291b68808c0d1584b9f200221ec0818c4598264328800c283e4f7eec90e2103e813d42f88162080309a29861e3c72b216c81f180ed47fbc0080aa01a61e3e70140c11b65cc60bdd086187f58614655f382124b7430465381cd0b41468098d010d37a410518485e40208606309068d0442438583a3065f8a1c100ec013e002304258bbcc0c8242a30629638010329ac80031812380904e203194c7c49440a32e6c0e31218be78f1011c0d303101802f3a40200f452e7062fad2a5cb1019f44ca08b2f00508014af3b9448c227101074f0401f3c0df0b9e2031e1104d82af029428191471f6c58f1d919828d158340b17d2cb862cc923200145ef418230a1d6cd474e0458a4ac2b8609b448b97af4b4390232209bde02f3787b02009245e6e420f889e93e8224708af295c4f7cd0c50083c41082235968d0451f32c820673882a5cb0a7c24e0c3f9a2e58224367869e40041645c48635f393388970c5c40028c0c048c1507b870a6834364e880121d179e0002c41a276dc4c185112e58ac3ebe2b5cc000438b1302d89071f121d741d44725657079e1830bd4b86137c1e583186e68e246ea881e4208915124808dd8a3450f296cc14514687a6a6032852240748d9e2c43126084070d027a4c8bac61460e1bba6ce123027974800336475b180089511a6da2c85bc2341fb81e7450c8162b3a4a2368a099624b043b00f1c8135c42d052471c76a800800468a105011b7cc143c6fb40cbea24891c4798205ae84e0ed9f0b243164be67002913d6ab8c9c28601829490a58d27b20ce08b2eba80e0db208baa8845d634c9e249962857cc6c51108108f7886cf3810ce8410257460a85ac30c249072e080bcc3088253d9438202c1e91c0bca91c36840a52a09492c0828811cf91050ec005962ea694d800844b07b0e4c0881c4cbce021002c2e7830481e812c02068b061008c1a80b225ae001a4cc20414958791e2bcc6809f9b0791e228d8c0f7b44f13c3a681cdd36393c4f054390a0a0fdefe421461a2220f1bf2345f604212dfcefb43c50428abb85c890ffdf2945a1c22a5001be841e3de8d31c3aa81e7288440634d61cd182996fe1c9b3c0c8a4af1158ff989411d0fc69842a41df8490ff4ff32711285037be43ce0d70122187ff9308ab073a89b0a5f5faf849842aefcaad9308524e2238f92fe2df490445fe4f2194398570c763a710be789dc3bcd2a7100c1084855c888f9f770a61e814421714847d99b340b03bca61984f2090f17f838105ef05091040f800e8c33f73fac000ab31bb56381a6f2eb8b140fcc1566376612c964e2af56f42584619659481731b4002045b9c3cc84e1c0c72e2204c9a4937c0c2d6b9e5b4c1b8c1f8ffa25136c46903281ee8526d6ee475ed8dbcb51abd30af70da00e7ff65c0030791c9881b300460bb4a59c2a0810376686025e88d3cfa50e38495422069a10c251040481523ae082730848c03a85145933416c1c14b05175884028e5cf91191e0c34a6800223ca0b132a4b8dae081ab3c864c395a63001ad962064b046a8865403083142db280a4831b8215521c4da8c18027c8b0f009b1c3255d82bca8a1e3001210682ab018193031059a282c902175650455133c5849ccb863043df22064e8f8c0004e907281cb494ef603217cc871830306091a4458fc20c4ca974b8cd823ca1b2b9c50e5032512a046070538faf8b9b102354646c10c1548a54e1ae09313362727588c21d8e980b144a9d4ff959598c1ece8ef48d4115b429fa7bf23311f6516df5f11b85920a855add7b7c18fb35447a16af3495fabd1c5f76905fada3da994e9052c4e395ffc09a79c2ea79c7acab15205c3524141d8286617868de077c2600d8ba5f37f03c1dfe0dcdc7c4b97b9c2fa748ebac73d3e86b9a0e74f1674395950c64de9268252044c989c2a08d2395590a3e54f1494f1281696f9e8e99c2858f3ff28f1d59d28e0b2277d9d28b8e0ffc5fc698209a0c48c49e9c1504722177536e7e26af4c84c702568628e193feee94f478ee13f4d10519d9aac3935493d6a14b990d04b94c2c5bcf23935b9f927e22278fd90985b975c3224e691e58ddf6b48cca14e93c92b10d4d1e2630fd813827af78069f1b187953fd01387c40cc40241309cb57b54ad304058f0942f2d7a5e38cf7223c9bfe9e6e5b9b22a9b280d954d11025941e2f752110d05617c357ae194233ef45ffac07f4992ff52241e38732a295194432a51d994af080cbb959857a7920b5b8979cb9f4a3b4c2aa5f906f5a4af2baa90bb8634c8aa62945b57267d5531caadd35f2266d67371fa26ef51095285a02a28ec4009927fb07f3d625e81e161968f0f81e36bcca318a25899952d39f38f42a1b011e42c91c589522c4ec459aad44acc2b4b9880e098e262c889788885a8959857e20634f05f894e255378d260f12883e1bcabc4234a86d82cee99f9c4e41db07f3d5ec4a30c06f411b9190abe4c1244e5038c7caa908f9e4a7397b882e6441fb8e3ff254123490b1f852912ec81a2ffcfcbab2b57aea452dc0b47954bfc40954a486c7dafac1a3f2e4efa820482312e4cfabac237c873a58ae663542abe415dc43f95b87b78c8b770d087c57a65c963de1c98a274812f59608fcf63c662e58b05829d4a1cfab27b267d355285111918c9b2089845d82c12c206b0060ca0c8218a78ff4928d2e47f911d940213a644705ee56a852b4458ff5f9ec40535a4730786a00f9343b8281d62f4433a04cbff7f6688212317b9ffd21dff253b6af82fd5f15fa2630e394a7114a208bf1245687b61a703a58ae663547a86966a86d6c6a4ec9e1c663055c617a872b5c295cad552b95aaf495f4262cb9481179e01842ce0ff7fde44481e24881f38821fd8fa71a30d423810248a17a407419a047204c81c8010d1a68cd21aff25359ca4f15f42e38c3fae30fd31c49bfef0e14d7fe4f0ff4c7432df92c30ce69fd4fd0b71ae56b885a7c7d50ab78c202b0c2befcc7ffe4b6590f147e1f8713bf31f2f2308fe8451a952299e1f0d4c91011f78b020c52184b4418505aac0a001131c9a743181a90b3992c0c25053fa1cf102041f04f9e2040c7ce8c204094858300970002eb898c2010e0ac0411c783800cb0d75000961c50f267dd4c00725260bf460da83cd1e388226f478630426ff4df4c0ab37e5c18688c7527980792c950708586a102c85c715580a8ff11f4be181e5b1141e39ff58ea8e3db0d41d4ffc63a93b56584a758714d08b044bd9510696b203e8ffedc0b0d47e531d5cd451039632d5c1011d6de8a0630b1d4e1e4bcd71c53f29037300c9a1084a0cf9c85be087a576de793970d068e159ac179938c6080a7a1d06c90830c64ca0f485042ca0a60a601105cac75998952ad8f65a51a04480b492c03f1bff252fbaf837e080430c515c6cf15d74807cbe9ce9f9f9b265fc40355e08ae82bc88f151715a74c284e1bca8b0fce8900123465546c74b10e7fd0d40dea8e38d32dee0024be5964af4302cd579a81aca6f44f158eae7cbcf0f87914002096fc0f0c699375a5085aa155678a3c93f6fbdbeac553c7c891f77631037e0f87fce52ad545ccc60911b59fc833e6eece0c60c2856de9f3784e1ff770373e3db98038eff1f73876146d94b1b54fcb751c3ff07f5bc46166b3cd1460bff6d3c51b5f198152b56d818c2268d2f02599df502c9087d01b77659c6d9d4369ae515155cc6956e7e7795946f7763520338ed6a9de59d595679ef775b198e222531809b956a95f38eeb6675a6f3284ec1699d55ad665ce20d04bb057038eb99b67a9b59b7e1cbbbc7caa402b8db71d574dbab7559eea616a5e0343c29ddbdf3deb1bd1aad09e0b44ab5d53a9bb5e67b33ae45c169d556ab7395566ef86a4da1e028ef28e5da669ea5dc349f08e0a4e4bc5f7ea5e4da866d9d07e06cb67d629a6d93f29b59bdba52ca4b7a82d3aca6d6e68ecbae65cfb4e804d7c99df3d69a4f8da7969336c1719e6bb633aa55d9b5aeddc904d7d96dee3dbbc63b4da7c62a959d06e0e8cd329debb5f7decd62b9ba72090e6b9cf7cc5a7c7957e5365dae5701389aabe4b3ebbe75186323be52a92401b86a73ed9c576cafd497d2d5957c646525382d25df36dd342dd36edfe40038da4d2dcfc9b39ae746e9a69104c0d56aab96dd6d579da7e5564e1d52125cd63bd39e332af7ed3deb5657eeec4282ebb0ccb28db9edeac6e5241dc175da6a58e3aa61aebb66edea4a11dc9aace1285a92115cde3793b3d66e937dde22fe5114a9082eeb785f49efdef9cecb2789088e6ed966bb5cb36c777b66beba9295c33944cf1dafb7726b27b799b6ba124ff9528dad8e49427077eb24e75ad6b65d35ef1d9e9256152908ddf1b6698ab76dafd6b9665f2a2e86d9a5c9d6eba33ea4355c46f7c67bd77177b74de23ae91612101cdf34ad67b2db7925d5c431cc2ed5e6a36a67d726433e6e2c907ee0faad16db8d4bca33bde1ed035bad96b7cdca917ce07ade66a7b7d6fba65be26c14bd2e1ba9072e5f4dd3b6ceadf54ceb3a3e728c493cdc296fd775f9da6e6eb766ab2b6bc8bfaf470c6907aef22a35bfd9e676632de78ca403a7bb4dcb79f7cdb3bc75bd9ce57a0165a41c3899755ddbbe37d67ac5545b5d69ade0c0496dd334cd769db3a45d0d23251e5b9ddec065fb6aa9c999fbf56ce06cc7dbd4b2aed9c4d996759d9dc1a0506287adcf1995500d5cefd26a5dee586a79bbbc73e5b0e87b5169270d5c5773d7699a6619573c3915b7ea734655468ddd2a9f984ed93bc65ab7f311d771e614638db5dae1cce28d8a91ee322c65763b4aaddef44b11b7d52e6f1dd62ad7b68d65df9f57f4bdc08f88ebb89e35677dadcd5a530cf6af477b5f06834281fdf354d7e78caabf66bd9adadbf16c6e5b27b9a6f99c51e991ab986f5bb659d749aeb56ea9b453e42cc6b7ebeaae9a7653d3737555a237e5167252ef7ab74c77b452dbf3b6bad2aa722854e5947706aeef3cef9d1bc69add7be7d595a31157a96c5e4df1867915c6c5751dc7595bbba736b37bfbea4a554675408e4b6efb9c5df7bed9cab3d5952ad59c2c16d7dac765db5dd969d6cd6b37cc2fb53e6754685a1ca5b2ab9d76a5a6aba6558942b1585c9b61717d5bed623aab9557f7a9adaedc9e8abb8ec6a34f07853afa3210f539a3e286b85ab37db5aceb33cbd3561464a894d3badd7ef5de72db7656893cceee6d6f926a5d9f12679d35da3d425ffe41a15028d105aae4f539a3da2b4e6f8cb1a55bc5145b9aef960971b6eebe49aeb7ed6a2deb74d2d7acae96475baf8f8eadde75cca9c6b7ccb9eb249796afaed42417ce6995eb2cf3ababd6b7dab7e65b058eaa2ba96c2e5f9d65946bdc333cb5beab2bf7f70243d591664673f86e5c52b9b7d45657cc6dc76575669a6b33e3d3de6df69ab99b2fd7b36add7618e7d105aa3659bb0471badbb4cb68be16e3ac8d3a1bf19945c5553a67bd1dbd9766de757775659512039737e536cbb6a6699b96f35d5d39f19686cb584a9d6787f9762bd6f8eaca51e455a2e1b2662dbdd84a6d9b36abdbea4a15ebd5ca64a6194d2ae4ed60db7a7d307059cd6abd9d96694b6f27f3ea4a114ba1cf1bdace70b6cf8e6bb9ab92f70c6f7275e5c759fbe32a6dd66a86b39ad6d17eebb53dd3b3eed595992c025d2ad6abb5597c5baf6fa70c97f5dcb0a6d19d37ad77aab590b1724e29379cdd2cbbf8ce39a7ec6eb6f1a639d572d737de211f7b7850a8cd3d17a8512854969f27990c4717490163389d4d6b7b77bbac93b472fd811bd7df972f8bc5a20b4c89e1b6bdb3db4ddbb46ddabee9d595630876d904206ed3b6e35ac6bacb3a3933dcf98fc8c7a39dfe249b2f96abe55d2bf4eaf87685d93d6f7d67ac799dfc5ad9f3832bc65dcd5cc31ae769575752175e98b4ce29b78a6f96596ca7a458676cbbbbf56bf9b6199d920b182eb31d979aa6f96a8eb5c67b839bc45f38d965ce70ef5b97554a6b5f5db9c1940f87a79e5ae6aab1d66dbaeebdc16cf3c2ed8c761befb0c5b26b5a73b675e1b62e534eb1d6bb8aa9e5747525cbd5f2549b877925e2eb0297b36dd2ae7bb75d9bedbc7352e1c2e56d675d36f3bdb69bfde695786d0f87b59678935daebdebbdf38f137115d80243979ca92d5cd7b56673ce7ae75db77b5d5de902c32d332d5cde92d62cc3586b736779aeaed49c48058e7466e13acef9b515df8ee79c7559a5bd318fe294d5e78c4a721cefb49a6937674d6b9f1685a2351c5d64b0703cbb9ada8e4a896dae1b0a85abd8d9ea7346258687db986f9b6bb65549afdce8ea4a292dae62a7f99c51653b1cbd99cce4cd19cf89335c5757621e823c611ed3ed68f2ea70b9d36d65ee995abd7558b6c065bbbb5be3bd3ba75a66b75a9f33aa1858e0baaee2dd6d3b77ba35bd67efa06aeb7285ab925bbbc9ae75adf2669d15aedb3acb9c6fdbcc33a393aeae545169add49c88f654e1f0963399d56d6f576e5defea4a950ef3a4fa3b12352c5438af5def36b65b959a56f1eaca30af74268d525bcbd5ca2da33acb3a4dabab2bc351c4d5e78c8a68e370ede4d6499be9cab3a47b7565fd40160f0ac53f90a533eb73460503e632aee9d6aebd73cfda51bbba5235a51d5b9ddacb751367dec98d5e7deb26efeaca4cf20d7e5a9552c7b64050c5daa14a2541e5aaac7ae7cdafd45bc392afae1c43dc82c66dd99dd9c699d669b867bd5757aec4974b1582ddca2e94eb1ab67b4fad9bd8e6abe1d595aa8b2747ef95dbde74ebb9d3b66b5757627925a7f58cd2ab5d8ebbedb2de56571681a1904ace8ce3dd6ebca9ee9addb89e7775e5feb29cf7099096273a4f789e0015e59f4ddeea73466566050e63dc3bbd718d37c6d7da27405a9e60cdc7a0509a135930289dddcc2fa538c35a76bca15028d4d4f814b951be697aef9c72525957575e1a2485ab5db65a96bcdfbad98ce3d5954f80b43ce16a3cf1f504a8cb13d5e7d9198eae2a01d8f0c5b3d2bb4d2aef866a11b158284e4f592bdedde6beedcc32d5c6472f2453c756a73c04758f0a9c96d9cd3dcbb7539dd16d47e1b68eb7e41df76cd377676d75e5d8eaf40990962742af8e6341a1c65647a178086a313fc9e6eb09d0932c3f4f9e007579a29a9acf199516282af115aa541ea4208d726b0414a40a640981ac0e4ea0d993144c8052021412a460298ca0820859085140c03e28c183199ad081ea040e660bd42a1eaa5472c8d6e9d25cbc1b1ca95426006073950a1ab49ca84270cc1948cd867032028b46114cb64e2c674a954ae8f356625e6118947001eee32b040b8cf84aa5d2a2d702a1821428103b98608260e88104433a772c16a842939d79e841042230912a2ed4010452c4915eab0542b9992594c2cc59fa0392541971105e851fa030d10589b7f287038589442aa0e0ddb0828dce77381b4e079d6fdde520550c69d0c070c64c193263c40085f979c1c74b1717b8f46cd19285c3c2b3a373d3c20d0b57fe66febf0a97fce9a6c99f6eb0446e14a1e0dd68001f3a3f800a469881b4812aff240d5c803406490c013c49e39a800f2651926025b1e594c4161deafc3f1620b4dc80c31a247ac86982e584c44760c5e988054c80039e8c78040a04fc3f01c496f761cb187aef6527f3eff3302a133b01fb38ebe3ac7a2a020d51071ec1e22120ff9940e288310fe63f90e14f44644c3630c419ff3c0bcf3fced77a1cd6d08f21c883238387b37a30ff3842fd1f8777f9ff26385b85a33fffe3043dd862fde3a87062380501654d114e1a34ff8f04107ce0c0f0ff4d70cebcd0e7f1a13caa58f907ec079c930f4a9c7600e1a443d0e720c7090729271c72e60d7adc0087993264fe710000040404043406470c90680368c3fec709f3f3ff02982f2ee470e1e2e385cb23727471e1b9fcff8bb8cc2d63cbd4325a195a6696f9c7e9996ad85023c6162d59381c2c3cff383bff8ff338f57f7b2a9c1658e069e932ff3791e05cd184b032a50a08537022d06ef4f080ca3f4eca4802461130c2c2480a23041831914a6918e7fee3d87a2abae31f070a9d325be11f073505070356ee4096967f1c2963985dff38016812be586036cb5bf11cb1be2f79ccfb1f47852847dc859b37fe71a0a470c487b279c435ebfde3a08063c20925c011f221ef03bd8cde278612a552632363065ada68e42cd66bc703f5e83dd27a7164400d6af087cc3fce1313fe874230cbd1ceff8d1c25fcffd1ce4d2424e08cf08f83e18cf08f23c23f4e08205c60ffe37c40ff713cb0238e36420a84609cc22e6e1ed97cf34f0c0e071f58088bf5f2f9b21730a398799751e71f678316f88559c68f772dee856b0f1c7314bec4cc43a00ff4bccf6b6d1dceca9ef6f68b0b27f284789795e6409c488fd1fbc4e456165f2ee000e08f3e9d39836906262d6df48fa381930cfe714e38a61c3930b8b9248ce6448f73018e05ff381550f0ff4d2ac189a0121c2638105482835309ce8d8853aa0487b4bd918be225bf2d49a5b44a2aa9a4924afeb3e8d0219dc730917056973f3160401f56e658401f238eb7321759390b8bf5cae2d219419f2f932895621db13e574b8c2b87409cc813411048e43c8bd0e7f9f09697592f30fcc48c21e852e38511370bdcc2595d1e419fff1b0fb058af2ce22b1c3f319b7f62fe6f3a00891e378f38f2ff250ce6186f0d2f8cff6f69236d0c1d23ca904a6944a9d4ff4d239891bb88dd40d580a6084d6496f9bf41e4907f598687dc90423240881f83f821c89aff9b407e7b807c9b29b48410117f74206bc70f3ffcf0c30f0c6c9647e6ffe6021ccbff8d05f6d8fc0b8ba5c7e62c561efd0b8ba58387f87f73c7f7eaff3776dcd4f1dbeba1e3ff668e0ff419b92852a213173a713437724c1a879515c0706847397cc38dffffc488622aa551a2546a6a9428959a3da914254a83afad1a9d57d2488c0a0c5574a85345f33157c0fef588573ede25043faf158232e4d6958f77e1adec6394bdb4f297c7bcafec9eeda942deed9e542ae4ae97d8023fbe5be0278ae2667964ae54d17c4c96efd5778ffeacc46398c11c6d70670cb36bf71c6d70e7ca9549ab4c2a66143f900793825f55f0942ffc4a93cd233ea4d9d7f6267d050172d304137cf4c2a29d79f87f638025fe6f0a704300cdbe26912ae8064509e2200a96ea60e721141503be135d227e693e46f45c3aa2e7ca421f6fe54fc572b53c31061a1ea808e451894f8a2406094e6980f82a22dfe0774ac381962b68d440d3e5e3ae2fa0cf098635679cc02f336dccc081e571be9801225122fec55319ecbf34c210efc3894c14aa1c74a0a1063250fef3281a9dc6f0f1e018d61829ff46fbcb406d80be0055f07f23c4ff4d10ff376b80f8bff9e1ffc687ff9b1e78f8bfd12187ff1cf21c72b5d65a6bd5344dd3344dd3344a29a594524ae79c73ce39e794524a29a594329bd9cc6636b399cd6c66339bd9dcb66ddbb66ddb368c31c618638cefbdf7de7befb5d65a6badb5b6d65a6badb5564dd3344dd3344da394524a29a574ce39e79c734e29a59452ca2ccbb22ccbb22c93dbb66ddbb66ddb8631c618638cf1bdf7de7befbdd65a6badb5d6d65a6badb5d6aa699aa6699aa66994524a29a594ce39e79c73ce29a594524a2933b9e16bab460318e066cebabd7d76d5ca2969be00a7b3ecba677aa3db9669335b80eb366d67797b27bb89e9cc7b705da699a69abeb5c3bdcbac07577bb6592bbbdd76955ce63cb84ceb5a6e3777adf9b6c98c0797e9ddb1ccb6ac4a2aadcc777059dd5bd6f149f5bdfd66d90e4e5bdd65df6cef757079ab39e72d35d9f1ad753a3839f3b619b7ddd2de6d3e07473b9ab37c6bc7efc6319783d3b6bcd90e57cda5ee57c7c16d5b57ae5d9e6939a5a6b70a707267be69b2ab75eb4dbb0d0e4eb395529dd5a9352c69dedee028cd7ccbb8ad9aad58db3637b87927c51b9e33bbd972bcb5c1e16ecbeecd5d6633d326ded8709a524cb1b59b6eb7db7763839b58dada5dae499be9aedb1adcdcb6ce566bf56e586edaa6069731963667ddb6b5de39ddd2e0f0ecb69c699657aa75463734b8de6599dc74b75aeb8ccfed0c6ecb6cdd74df14f77bf7dccce030a67deea975dbd5369d5b199ca5dc72dae14d37dcb9dcc8e0e6a6f54de74cd22cdbb0dc28c0ddd9b156edd554cb3096db189cdeb2945ab6d53ae5ec5d0c8e629c6dd56aa71be5b887c155bbbbcb73d637eb7ddb60701ab5945ad9dd7b6fa57b025cb76592f2cb35df1867fb0b4eee3bb9964dabcdec728d25c0757ceb3ad34dd2cbb5d4d80b6e4aabfbcd73eb364c7787bbe0e8e4396b936a92ef6c76980b2e6fbdbb3cf7cc55e76ec35b7055db9cf55a75ddf4de174780db946f9c49cdefdc38be580b4eca0cd79bafcc53dacce22c382d6f9d627a7bd7f5de2a8600a759bc352df339b5dde18a1fc06d9bc6f5a6946ab3e64b31169cd6376f2d4f9d759a6d135fc1cd5b719658df8dbba94d6c05b7e94dbb399bbbd6ac3789abe07495f2f22a65ef7ab3123b809372e20d6fb74b9bd99e612ab86d2bed7766bbd352c3196e006737def1ae6bdd96bb4deb0ce0b6b933dae7bd35eb6ade53709ac6a5ae35bbd2dab9ed057035eb7ae3cd6b26bbad6e05705477b6f78d73cb6d9e5b0a0e6f934eda6d7a37cfae4e00c771c57c6a7a37af9deca2e0f4cd726f5a3631be5ddda0e0b896d9deb3dd689731dd10c0d1ae719bfbee5dcecaf13d0037a5ddd7f22d6b999df6de27b84edb79ebba6d5e27b8dcf54def5d7967e5c66d131cc69ace73a39273d9676582b355d335eb6eaef8d2cd009cce7a5eaa6935bbf95e6d090e671995176f38d3b6566901386977766f3771be1ded04e0e8bcf5ca5e2fef56cfa904276fed1defb2da5d4efb00389e31b7b4ead94d9eb500388ebbbe1aaf99f76e7649703d9b5be5936fdde16c4382db99ca6d9399b6b599a93c829b96decdb73bb5a95d6d044731cd18732de79cb599dd2238ab759dde2eef1c5bd989e032baada5fce23bf3d64370b7bb1c4bac717b2304d7ed2ea5dd7a6e4dcb346e6f82e0b66d2dd65db6ddd6f1bbedcd1aaea398667ae76c4b7dbbb63740709de5527659b651bbb3adedcd0fdc9db672dd499ce7e4b7b3373e705d66f7d2aab5cedef4c0ed6ccb72a737dea7b43ddb1b1eb89975726bf9d29a6daa657bb303b76d384bddf3ce36dad9cddee8c0f59d3bad4a6dcf8c5b6db337397033d31bcfa89dd26e3a377bc9dd64b6b186b58d2de1a886b194dd6eed56acb54a38492fe61bdeb4c3bb7694128e565dbbbcd672996d9f84b339ab96669ba6f52bf5fc0077336d6bb6f6562d379d92703debac4dbd377d71ed32126ecb6c77e636eb9b67951ee0b68eb36cca4eb74b67ef00a73bbced59bb2d2def1b245cefe89675d69dec12cbcc3ec2557bf9de68a7ad9659965947b89e513e6f969de33e39073889a7ccb863ace13d7123dca41c5339f5a4bb4adc19e16a96dcf29aed964969fb221cb795f32a3b29b7e67b039c9d536e1deedbaeb7db34c0cd99713c69d5b44cdb4d11aed3ba27af55eb368d529e08d7ef9552eabd5daee7e48870d86ed66a54cf3aa9d60fe16c9675bc5a3ee9cdba6e43b829a5e5d66e1bbd999cbb10ae67ddeeabb3a9b33abbce00a779cedb36358df1d66d1d219ceeb8b4d9be5bd7b3a6dd20dcd5dbce9a9cb367dba66d827056b35ad536cdb4a6d5b905c25d9ceddb6d7bb39ae70d03c2e9d95d3cb3ade3d9a5196ec36df66ab3cba9f3b59dfe83cbec9e77cb769e7552ed075771dfe6cd7857b5aedddb07d775c6b85e4bf14635bd7c7032ebb34b4a71c615530c7012d36e4e39355e6996f6025cdd5c6b9d77b5f7ad336b018e4e9e4d5e379bedc9376d0f4ee69cf3cd7ddb9c655a0faec3b94f5e6f96f39e95e6c1ddae6bde32de328b6946f1e0ba6dcbdde6bcb37df67e07a7e58df699354e33cbb71ddcad53d36ecf32d7cce6590797abb6cddaf79c950e8e6b5a97cd6c536ceb95b2cec1758d354b73d559958393756eb36b33935d27b7acc6c1c92cb336ebe49654e3b75780a338ebdc527d67957d6e38b89a7bdebacb6f3625dddd1b9ca6b5cc25de9276ba6ad90dae6f5aee76ee2df5ec66d6dae076b73bca7bdd9da4546e1a1bce624baba6b7ba33aa69acb1c1d92ddb8d6b58cb8ede8bb535b8ca6d2737aa77c6ddcd575383d31ddd709dbd6f3ae3d86a697099cdb8a6a5961acd19b61a1a9cc6edc455673df194d56a67703ce32b33dad13c3395553383cbfa7633e3b777195359b532b839338bb3a665536b8d4f8d0c8e675a5727bf39a35d66a546018ed739795779c65cf32ab531b84badadd652aa6ded32d3c4e0322d27a677cbb0ddd9ee61705c739bcdbbe96aed963b181cc77cd3a6beb76a5bdff804b8aafbc4dacc38be5bbf9b7ec1d53df196f1ed5aefa4d554025ce61c671dc699d6b74c3bea05277b97d9a9efc59a67bcd12eb8aecbae7667d76f8775b6512eb89b6d9ed999ed5a33db31dd82e3ba6ab9eb2d67d52cc63402dc262da7db9452db8db74bb5e028be5b675cdf2eabc49666c16dbdd92ceb6637e9d6ed4a21c06dd4eedbb5b6dd9a71d7e803b86933596bd57066afc627c582cbf26af3d69cb3997597f40aaed3aadda89639ab97ee8c5ac169ddb74ceeda751dd6ba57c1757de20df79dedae5edd1dc0653d93b3e22df32ebbec5470d25e796f2625dd5ad6f106709572996d5bedac26b78d3380a399d5f8e59a66e726279f82dbb2e34967edbaacab782f80b372eabecdcd62ad6f762b80cbb56f586ab8a3b566754bc1d94c6ff2ce7df3b47cee0470d26afdced96d996f36eb28b8aabb59b58d6b7df3d67550707a5e2aa59675dda4951d02b86eeba6be99ef3c3b29bb037056cbb6eb18f3dbd5aee627b8aef2aa75daada59d9ed9096eebeebdd856ce35d6786b82dbd9ae76579bd1ce4ebb31c1610dcf69bbd4dad6e1ba1980bb7bdeda75d699d6f2dc96e07836abac1ace3aeb756e05e064b7946a3cbb6eab966d04e02ecd36c59dc637d678632538aab9d6b36df3eeb9673c00aef2d95dad6d6dd6db2f1600a775b4da8bf5cdb3e3dd9d497059a632677a6b7c6f9bbd13094e5bce65a775eb78b7dbce23b86badbef96eb976dded751ac175deb7c41bd72e6f18d7592a82e373eb137756726bb756678908aecaae4b69b34c6ab94f3a4b4370ba6a8bb9de3aaddad6739684e076e7b3ab946e5965a7e52c05c1c9de6f87b78d377af52667690dd7b36ccecd6a6d6fde59394b407072db9dec5dc519b67cca59fa81eb34e318d7aca35b777b364b3e7052f73975cf324b336ab359ea81d3b6acf14d777b6696e35ee281e3bdab756a56df3cf9eca51d38adf1a6dbeeaef596b39774e0b2ae756dea9a6d97edca4b39705b9d9df2a969dd66adf525dca5566695de4d77b7c39d25dca5f556dcf5bcdd3d6d5709b7a7cd7096f3a43c9bb5a384b31adea4d66bc59d76ad9b84db5ad355e65deb366dcf1fe0b46de39d4a8d6b2dd32e4bc2651ddf4d5e8ef7a6379b23e17637b7bed979ebcc3abd7980cbbdb3759392caaeceb9c90e70dbe476d68cdb9d6d77af8484ab53bbf56edaf9ec32bbf2112e937377bde16c5ed9bb958e705aa3dbbcdbcdeea69a57c9012e6b3cd3f8eacc52acdd2a1be13aedac56ebce58d7de51c9085737cdaac45ad357634be5225cddf2deb64a8d67adbb2937c0dd3c79363bab7937bb3ba50638ba51dabb9e657eaba64da908a7e14de9ec66ae9d96d9948970d46e35e3184f8e2bb7994484cbae86ad76f3a438d33293877012ebc9b70cd78b75b7dc102ebb7dcbb49ce1d9bb4e79219ccd327badae74e35b963c035cd6b3db517b6ba51ade9b106edb6dcb95d6aeeb8beb1e84d39ddcd4de9b6d5addb42d0887b73e27cf78a5dae64e07c2f5dd513bbbb6f5d4eca601e1a4b69ddb9a7bd786ebeca5b7d73a2db55beffee0f2cc946b75675a26b399fde06697338b279fdb66b96d7d7073f7db651bad739bbb6e7c70b6663ce34edbac96718b31c0f5acd36ebbdd33d592e20b7074b3fad2abf7ce5d66d8025c86b9d5baa376ef8daddd83ebb5de2eafb4fbcece563db85c69c73467b8c37367350fce729a352d77de6456b58607c76b97498ab3d69aec92dec16d566fb593b8dadd6d46ede0fad6328a6fcd7570f46eac7515535cb3bc493a38dbe56ce26d66b6dacde7e0b8deb66a69e63a6b9d66d9eaca2c4f80ba3c11c75c5955999483937ba3575a2db59d99c63d0e8ed3ab51a9bbed76dbe67aab0057e7ecf8ec776a9b6f9673144ab33e6754694870f48977c76bd6abd639bfc1e54ae9ae5a6a56d32496d80d2eebf4cdd9ccf8a5bdeb6a6d83e359ae1c7775674eaf76d964594729be75d3f76a6c27890d8ecac937be353e2bd619add5e78cea87b40667bbadab755259b7a651cc5f6a709b9c985fbce53dabb6694f83a3597627ed7aefa8b4f26a121a5c97e93d71b5156b9c35953293cee09bdb3e77966dd9d6bab6ba52b3976446df19df55cbf2cebd0cae6e724a3e33bd3b5b69af1a195cef7abbf7e69cafbe15570a7092f7cd6eb2d24ce22a311e83b313dfed7619576eebed5c0cee66dddcb4e376cb1c5fad85c171adcbf36a6bb76cf19e6070b6cf69a7ceb8f78ef9ee564e80c3b493bce3986ab6ebb674b9c47cc151cd52ad4dde3b35e25b425e047699319024c061b9d15da7d6e5966baeb50791bce02aee996b2de70ccbadab952b52179ced59bd9d9c5aef9bd6d5cc0527ebecdb66bbae62dbb5a9b7e0aad62dae33d39abc55667475257e02a4e58966bd302d30cc8242a150e1285a2152043899c96c3bab2f973a5f2d06850243aee6033f14aaf5faaccf195547d282cbf26eadcdcee6daed9a0537b7aa69b961ab67a62b95aa4d561204382debf6a6bbec5abbd169b330e11846ca1b8e22253d80db2e9f966a1c4f8dd2be65ab2b711f5f9b88c351a4948405b777c6bb3c67b66972bbf3aa3ecebaf2c805eabaebeb09d093aebe9e6c937405b7ddda517cb77d6ba75da6cd1648567059e25ef9c66d576d77a5b658485570fb76f4f63cb72d778aab740097e9bc03a074a864f0aaa6a198a41432840000a8361200900003130030482c188c0583d1a0585dda071480024f966eb646984ca4d130475110c430c810630000000040000186a0b41100a84f064edb105d0b793131399a8b74cc223ae3d69f0073ec8df9cbf13bdecf59cd26d2ded26125f727564e1f444f85e031698ef8445a593a5909fbe4e34405d1a4d02d2ede7eaafa48ce6dc1fd6d43a12429448be9e7e84a2488256d25be4f2b4e271061857c3151399a8874b3c45b49f58b24b86dbfbffa50e724c1d1f6fbd577750e1298e6883db17481b467da271eee4f8cdbe69eb5fafec70c41ba775badf13b680a907ccfadb6bf83288e2435323b50a13bd0faaa2141072be38b87cacd442ca7b191b0702cffccf8e8d0109779465761ec25be97a0dc9bb0d73ab973a6e0c8f7db6afb3b880a50e28826d28fc0ab3a21e7b4c0f8765cd57872120bceb71b559d9373b7e078fba9ea2339b705f7b71faa3ec9315a80bf3dafea831cd382e4db6f55dfc9312c48df7e54b54d0ede82c4b76f91d15f5a1fa2725198a2c89d9b594434abf8e70ac17def67ade689b46469be92f6898fd30aa29342584c3e4754224d2cdd56e27d52714552df76ee2f3094238948344bbf95ae3e41386910f1155ac574e20813c967896aa5c9af5b70bcfd54f5919cdb82fbdb0f559fe41c2db8df7e57f541ce69c1f1edb7aaefe41c169c6f3faafa26e76ec1f1f653d547726e0beccf114a248925da4abf3f577c20d06865f8e259b9391107b3e35b87eacb84d30d225e21554c24ee6de7fe8243ed27a98f76ee17dcd57e906a9a487c96562b9d7cc238f920a22a3489e9e6882792ca12c94adba73fae50f549ced182fbed77551fe49c161c7b5bf9d3aa92f8a58713475c5ff3dc5b70fe7e86fab6cf5d83f3f663aa6f7eee161cbefdb0131139003d2777c22cae4be51bed9ad641f7dbd4558c26063351566cccdb684295b3c67d4b1a972bf42e6628b50823d7a1741067cbc0f0b06a51a871d6cc88fdd55d977ac1bcaf45aba283c4dbd4aa220f0c48ceff83289e91a71fa2339f6cbb2c05375468382182ee1591245004ddced938dd257acf72511b06b42dbcd7cc5c9757b97017b623c10020db63ec15d34ff3a70487527f119a0e0e7f927b6456ef440fddc74a4ccd8063af5f6da1b3826f2c9d1a00c7593de32c3862562a63b3335a8ec532809e7434459c054bd9c675df3f240b64dce48ee6fc2703b22f87b26f654a8b00591936ce8dcd42ff068363792390e08044ec88abd2c2cc41a8d6679f2d2d4b4f2edeadd6147edf4b20c57b01d1e4cd8a04d9d00ca5db0a6a44f3f501fe694d41e4fb92745058d05b0c300fc77ad6be383c05bb5eaaa935db547d21a58c5a27aa56b37642267145578a58eacb4fc82cc621ea47675a04a8616bfd866cd742924f03917e93925c96029ff0acf18c5690d8cec08c20a06592b9ef79ed8c7b503639bfde91499940a2946a10c0042a0da05395091c4e62ab5a85740bd44fb12d52a8217a272861e64b9636a82c36a5d816c5215c04e3f919e566234b03d23500f0506303076838c0008d074e80c652636071bb428018fca2b9447dbd03978429e60741f42101b11cb280580c19215286d25fed3bcc6387868ef3a224489703824859b07796127cd28a4ee2520ba528c7bb0aaa58b7a68aee46f76f03d0c3a7ca8d41ed04df1e8e616f6d7dda92afb667d3213c85cceff1a540bdeddf717fddc84f057592104185d5a0ccc16b5682d8333bfc5f5c009b5e6bfede6ca3736ab277687be4f3c70370d5f6b2a8a7c8c893517760c6792ac52bac5ebe7ff6f4ece7ec81249585d0c86856b0ad7240192b0d425daf85d6d0f6a47689d7b6d1d71d062d00178acb021437250da88f8582201258525546c662596a1fb8de0ab92f251b66d51f801e2c8150fce3fc01a8d706a06a30e82a595b0a836cf1e994a016177492e953f2c9af9f1d15a2efbe15a67133cd45637f25e0e49c9ca7406969a83331e20d1850566f110cfcca4ee3338a38bbc290843d0c19203071f7c15c0971f9c0dc94c62c2b4c50d4a1da2d0115ab0651043a974629b3d409e6baa08c015f705810faeeab2bbb6a16f2afcd223ba1fba120b3345f7b7bf66a6ec7467e59b1951b9c08f2a58ee7095256e12bdb162c1085b9586b819f1ba9cf98002768be53cd458c3141bb131a433c7e523ec2d83843d3209b3be53bdb8fea632781143bb52a0cbd398bb18020489811a6e2d5495b5ed523acc69861d35630589943277d4466b729e0477e350ceac633d26eb1e1cdec138b19a84addddb5bc649b08dbefdffe33f818f143019080f25a898a406c5d32e99e6e9f90a71be9443a43acb4fe442fa9aaba68c089577e82fdfac75df2b9a9550e3c702da81d17426a5baa8f9c0c868223976f58cba360fad4f91ca112a0a6f4b7a0bdc183306086c9f122e7b7ddae4e2ddc5d9ef13ee149fa5bf16db12781f1073a0bf52d210d2ba3dd4241a1690da267ab615aa0af49158f4c2f5651d73eaea80bd0d09a494d3799fdb38aee8b4c6dd4694b250b4c9b0f1c002fb54070d8af243e24725994aacdfc6b5393957903b5a25e30506274d303d4328dec9856e98fe17cc122fa8b41ff5aab83cb300422b6d9416d6503b619762441216569998685a2a6db56dfb0482c849c1e0cff2a379a83771b807d340b6067b3ecd53ae9345b66fd0bc0937d07aa678a9652baaec54d20e88d272a47165098a11341230fee27355ed5a130b6ce4fe19375efb58e51b4caefaa6e1b7bd7d67ac79c757743538e2993eb21784e8f1aae94202fdadd01b195ea3810a0068eccbefd77a05bc8459006c0934f418b222201f50a939b015acc2a39dffea1bb78e0010cc0cc96fe21cb26d69208dc5d261150e66f40cf63663ffc345acd69d188b2f87d438da91e05943ecc52461a4305c28546c9fca24162061f32dca3dda62aab9e6f07da2508813ad491d7083e184f854368214f2d6f968408498944de58121ab8562f40282a6ab59e4780a830932aeea09b522309d47b1691e64bf9fb78205b6942d0984c8701c67fc9e83e0a4c63631706004284a5a719200141d970d40f2dd8883aff3c927f811cfb77d542fcd86324e2e3b7c25b006a30faa5148322d432376d7c74f592d8985231d31a7c994678628e8007695d69cda5dc299f2a1c6f067868d74ead6e43d0ba4b46eed8b995e742b1a15e5fafd971893ecfd227677661e9d18e47ca6486886214f66f41b47d7f1635ad3a77723c88187fcda7e5a5eecb7d304a95cc81fa114279a0ff888621c1307fd2b91338fe6638b1c1382465e8f7b765b6fbe4204c146c239acd9a27d24c0b5f035ef7a96333bf86c9533acb0c63ecf6665e39dda1d649b366f63126f777bbfe7993d035d02e98de47a5d150534d87f506c02d86747ff8893722011cc17610e3dc8b44bc181218a0dc1d41408d4636e1243205660cd1d218a4ca2cf9701230e799ebe794b872d124fd26621d28f7ec2ef626a80d86379046076b130a3734c8ae82b3ba3b8c86d55cd602cfb4f88002ca6390a42dc8dbe0eb6503a7bfd633d2244484dd10ee67f0169fecce4f9ccee423874a8a21155604f487384016f2c648c92d173c9e17f88a552304ef546003b10a12bdd0439f37ca4090384e4d0dd040cdb24797b1ca2313055092c137d30efcf7d243067259794450fa30639a53906a3edb0f0885349f1f70bc593d1f025b715dfec370a33c70cbeec4fbef8d9180c8f50b8c38dc7705fd81c00bb70c77f49426cc8dc2b4f9a269e6b63bf0f4b37ae56f5a5194c70c592c9bcaf0da681fc3a389fb367471ebbf6aa6069d77243c66c7b7e47690273e1e00a47c73087e19460f168b862f3bc88fe0c25627d127ea1273f9cb29d937f3bbc5a684b27465aebaa78c3407de98f4a6a235b9fc6407e0d6797f9002ced1df50284a797874290bfd8285e54c39ea1ee45bf7ea0e3941f86e5b3f6ecd9396710ff8266c7687d78cf82858c639033abd7ff1630f58000d7334af193adae6e8ed65dbbcf184c13b3e5feb84b8a0ea81f0e8db8237e9692f3b682b9bb8d5c8b1ac9c61e61d6d5b7eedc00a62e04b5bbba83c00fbb27db3bff4caecd58ba741e7e9f8c4f82c1b74713acd02077ea690cfcfc3d1988bcc195fd032cc0c793a1abf71dbecd82ce498ea8a0f59d8445d7062b536e742e9ace3cb63c8e33465441b75f882c5cac3ab8d33a127b343c7f67843f9f5286f0fc05f0a80aa5e5eecacfa6799fc3b9599ee35ba2c802d0ff9411119a7c44fd970a2dc3b36df032afbe67508436c6f86caaef641d25b6c6ea32a6ec250e36a60fff31d1d2fd89b01a4c799bd06a11d9a9d8ba4aeb177a83e1e40e717460b735d85b7c554c752af5b855e4386c2b1955f27813d8a84b830ad453948e994346fe63bd886c1be1b69d7af50ef7854062155e25f726c4345ae66f0fc65dcaff0a09687a08d4e8b3e9740f2487caccafcb0c53d6fdccb029a7535c37c286c94494756f7fc08036a250cbfffcbf30907b3bc7ddc2cb0241ecc502ed711ba7b7f0eb48acab3fa5ebe350e278c9a419ef175a34dc3470ab3605bd63b85bb3a5618cf725bbd77cce97cb6a5e57d5598c376cd1bea6870d941c0b2beb12b45c3d906194562a6fc77694c69bb0e2fd010bb6f065ff8d50cfcf9dcd71f293b20a8e7b303be4022fae9c27439d65714ad624d11a4b5917c92a0dfd7e7ea5524ff7aa86ed9db6546e9f1f7fc4bc43f9e38c4e087ffb07a001a04116210c3b2fbd36cd32eaeaaf0fcc224a9130ef38410ec861312502f85a0bdc19d7e53589463fe51052ad0a0dd5c9a395df2b1ea69d3f0eb26494e17e743b7b063e979448be503e3df81a8ae0ae08ef727c1592bd6a7bcc7c3bf4befceac5958491324f0a7ebd1202850f452a1f9b7fb240880fa20e1cb56ffcec9ea0acdb93664ef69bcb49dcc2b759a83b1a9578c9bdc21c90fbb5f83bf2cbce78f255ce4f2ce6f51ee3e3da43f0d1cbdf7c3fbcdbd9f62aa3932efc250bd2fed8ba418d61e1ef8a3bf6fb11ddce2d7984cea7e94b0d5d479aab6b1864dcc5cc0e3962f6a282c953ed9c3bb2874e1dee7d5e3bbfdeb52063b3b4e400ed7b0aeadb4dc90700da3a330e707978eb53309cba8c326e2f527b8e28297fe90ea9f34a84437eb330f90f0dccbc077972e8ba0ee5674328d4702bc169d3fd11e582ea0586231a8afcfd8c28bdc73206c86865eae998541151b53ae67ffb030061aabf919f377ce37379a28fd42a7bdf870782098a492b3f4e629c3706837c24f62aec21dbdb75644f5a661eb83a25226ef6c543d33c1a3d0d8480b354f569f4dfb5433776a32bb9b93f2dc592ba8332cb00c6d182bc5c0a00695788311c0e9edffeb2afac896fecb847586f4402d80720e4e978dc96bdd0f05dfcf7092cd8c1c7c379bd71f6f5e106b095386f50ad85d1100803c807a41e392b0fbe9de67d042815f7b88fa110678da3174726f28ea87af1e6edb0003569e6729e812e90845ab0f6f60b90925363f345f362dab0e30de574902915dde25fdc42dd65cf8a55360c62339dd8824b55046dfd0da75219d32a27690c27813812246f9d4b309542ba05687cd7930cc8f34c3410b3477c614df311f52ab4486954e788601d6f3785f7f7a9c19e0bc155a14c73fdac54c9b86291d4893dc7f9d9f33c7c8736f00783af8ca85937779d1c4d064044bcff3059ec3d58e6279beceb10e777b53490aee47d357be9295ff70dca766ba84c2ea8f59361050d7ac7c4ec0f9b0021eacac5a954470846d39c72b7f7791d28f574c619c3c0438f53eccb16f2c77aa7b310f9fad31272802939db3222204f47463a2ba2d88592d19a994254af629e3b3c173be93a355b49b219586824b07215b30d61a77a3233a699232339291bee5618865239a4e6b3b3224bc921950b372b1e561051664489c11f94a9777a3ea538974ef2b7b62982879d0ec5089fcfc5877816eede4920ea59fa968dbfdd0b736be67d8eeb8866a07a63a1297a85e5a68a1269c42804dcc1b0791e6bed355ea58cb7e4a16b35165353049c470c342843234d19ec5f8c50c211e3bc0164db7054d2bdd399d8b1bbbd6b1d894af2e9794c1f00753eb58cd753be90958a2112b311bad3ce423614b68ea0263e8f41a5eaca813f356ca7383e710fc30cc1d033aa5c63cf680ef06d81498f1ce9ee2c0fd2692fc5eef2738afaeb5f680f3c9aced0799643cbeb507a70db022bb8f112073c82f0079306d61de771b2dbb9f3cb6f3508d49f5bad62313bdb066849d0ffccd277f0b70f80f983f422f88e3ab93052f18231fc1fefb535a0b7908f10eedf2c0350a9d584497e8e442eb07e60694bb6adac2f03f0aa963753777d961456f7a461c95dd40b7b70cd75efe0932e53e63aab3f9e16f0cd9139cfec5b51f434d26952bd8b75f154bcf23dc85cc6c422bc5df6eb1174a021accf49b72f81584e707b464209aad63d54988662be024f537165149dc90d0ab848e97cc4ab748f7b282c2b4cc90c94c9c29667fd549d7f2a0ac33bf8b88ae418bd75086bfda5c64dd2ca213fa6d3bac9ab90903e8be0ae74eb4a26180349513d3d0710299414ae391c899643bd8f7d8ddb69a84ed73873c28c5af4f7e9a5c1d43fd699dd49c24cf2844d7de803eba26b6f03129eab1add8102a75acd8a9043796def1a0c76170cfe02b4247094a6833c94b0492a4ff2736f395b85521b2f3561f8e8bd87e7ef65dde94a6d5dd118ee8a97a3e134465623347f0330f8fd64f163410cf7d67ca30ff31079a18d1a08e7c1e63f41da4ddc6dbe1c752fa523a656f2964c2f44a40e05ec9c433cab8977bf95c620a76ef3b1351ae4d90010b16ccf0136c5089a9e36540b9e8b098d60ac22f47c55ceea8986bbe424bcbc662e897ce9004e5645c09ba48187b9e9a269e60ed013f46bb7218de6d443f7e03f3fd6e96536763536777bb41d2becb3afbdad72b208bbbe0885f1f333bbe0c4996b33072d7e3ec0d6840ee4586c225573ea0e54fb424cf69a6bca3e6daa9cb339789d02c39ea40916831dc17f6e210fed7428b61f1876d3b54bfbb36133228162fe54470ceee6efae0660831c68fa69e378a38fcacf66efa162333fe8908fcc40d5d3595d291cd3e0ef4b11f1b270a1d1958eee069752eebda237d3fa1c6ab5bb406bad81deffd126e94c9316427e68e7d9f8634cef75e294d7f91a1de5dd08cff1289331a8be7d8f0c7d2e5cae67a9a15aca19fc807bfef99e87321f82448c7cfba651bfa9ea1f796613b7b095e513dce29e4e76ad0c2fcffdb7aeb207bfaadc5bfb01800dffaa2dfef8576b266bd9f40f2ecc287b29be57834fbd2a39a2b9800dfccfebf1de9272f524f9e9e46160ca3d5f5ff4a65fe5b40441ad5fe21c70251f3be35d8c86b460bc2eb2155c0327e055b91af8ed4128bd93bc04a7e3ba693580d6f400bc2eb2155c0327e05598bfcd39b69e409fd73c32262f81c63b40c874f4a93c5f19a6467ba3f51cd6c8777b405f3fe456d633bbd50ac0c974f4a93c5f19a6467ba3f51cd6c8777b405f3fe456d633bbd50ac0c974f4a93c5f19a6467ba3f51cd6c8777b4054b474a97092dbdc12d619b8fb5e5cae631b654d7bcb796a99a8fa995ff46143ff2492c37f6607d7e046cc76de907c749c1e02da3238fcf6b466242f7af90de740f978135389fff71db711a3aa071a5f079cba84860f399919fd7fd23a030db8b6b7c0dcee547d86e9cbe7e681c19301150ba1aed2574f2d98773a6d93373a265f361a33eadf2a7115add60412b9728c2b784f5dcbebd3697bd648a7c576a469267d6efafd384e41681ae8d89a8f1beaed1235df70bf8cb883c6de4b20d14463e66b9d10d5c86e1d0cb5dea1533706b05bbc18665270c134c3fbf11438a885513e3c376dcdfe54b48cacbe1d9da8a39bc27c366fa0795424fbc84c35555436d0ee68b76ba347596425fe5f1ef86f518e0951a00a2a0ee7f8ae6e0a87e51e5d65ccb9d117e7d228578585e8721c0e4d3b44ddf7ae4808d954d7478cb196fd2c7daefd884f7cca906dd403df240410a9d7ddf430b7744af340871c76be5bf2f2d35c3fa2b03c2ec433a502a48bd27da70db081a25d857a5c22447492fa26483b25bafc5a78cd400e0f5955c6ca54bbff5a1f30fa5c7bcc74ace93b959451baf420d967c8ff20260d2a0532d4d09fae3d99e54e811d41293d1d08f2382755d10773405613c407a572d9b2976b9caf907c840b2ba061662047426cefdfc8b991539d30ad44eeff2f8a733bb5dbf978f88b8eecda197560ca0ab914ee6efc07dc657707129a0ede8b27dfd73ecd295af8483de39b3aaa1a01489998fa7d05b16b7321e24b654fca8e3aff832a7b11b6cf6451b758c0367e8df767fcd810b733f8cf2761037e11df8978a0db311bd26b2e948179d3e1ebed69b4cf6e0044d7d5422c24c50c1a1efa4ba1445b27a88a71a64afade3dc19a6138b7ba4c5799cd477634732945fa1daae70060089c8c42b06ab2aad81e9d97b477b581e8ad4330244a3e19fdc8e3101d07974ad0af7c82460aa28d56def2a13a6ea98720a7dcb4afe7127cca4dedba9b6dafa6575db1562e73840c1f54723f25d5856f31daedd1dae2e1a88d823ffe15dc46d61844f27cd61cdb330568236cb828062f049cef86e381b1036d5927e35703406c5b5c24673ec7df8eda0bd583a4b6806432845b794a5975798d3c1680d82aadcdd143672e36a5ddf40219f22bf1b0da978e54c322cd95461ef80eadf349758f71108622b9b77df16e16585ccbf1fd10614a952be44acbd1cd2cf8e2a3974ff264a0800ad9e44703aa0a02684b2dadce19bc8eb1e45895647530785b010eb59e362c278ea5a04ad6b9f52a7880783751cb83658bf5a644a73ae3149e8c4b8fcc550d3e7189b5511b50f837d37637b990e9b2b08611ced687b940b52b903a109bf5b397b87ed3ae7a15c27aad144be162818cf48b7f96cd85e88cf78f5577e39544c07a49d81a9dcc0e10b5ea437a894d8ce19348441d2836fd0d1efc85f190dc00ae3fd43fb9c827678a616feee4fac0bf77f5415858e13f55be29df0231ea6b0563a9f79048751e4f29612a40bb0d470741628b3bd7f87f8e3dc0e859fedc222559099382dcc958912080c32ceb2055c8b52cfc16e8ce32a29e5aa591845b0ba727565f69e06dd225975bfe9c44f12b903bb73c9feb38c4eafb8b80587de5a9627cafa6c0b3d7f1ada1c99de151d4c4136025b871d588415a0bda204ad1524ebcd8641648f42c1014bd654c5630c3dc1fd8ad1fbebadd88d316d2564ff6ca5b62610b41a1ac241c1fe7e0285d25136152734f4165212be3ba69aa93acec6507653ec416fd9eaca4c36c228ee7b4819594a885e234eef5bfec41c85dbdbbc117cad704f6203e3af03d93919975b63277396f8bf5f19197216116dd644cf3900495fcf831c70b7c0ce83f3e8e78b8a3f299f6d2139ff517dec292c962dc4fbcf44c433a8ae338c243b4b4c8e6ec0e4d9bd854d1f8989fc4f8be4aa8563e15d47c0b81fa6a9edfa6fe88149d25de6988b2bf2e72854d20be5da58bb7f64351d3a893e6238effa9d4f26d19d9673857613b158a331b492bdc4d38dcb2cfcb6e045b83a41a79aa8a93eb89f518de329a0bcdfebd0fb7cf184a95599a2977b2c5e917bd58cec135cb0551d3f487f971b6db3c170d6ccb36f02e13f4877a0778d6ed3359564f18b83567931cf18a0d3dc5573f9c2c498fcaacebe869de077d0dd782863830c5c2cd40e5a3910dc2e159dd3711b16c08e9dc3223f08612bbb84111d6233aa9ddc0eeb0083df6c560699c151bca4fec73c967288e6c516bf985657bd2d5ef3e36ca94eb6d42c2881d3ce1151f4fe3def1ea967cc7f433f66e12a6dac1e1cfacdd9bc779e7d3cdb503caf9b660d3e2e2ee74824c2523786c280ddfba7d7a6a21374c918e20e3a4c103b2814e6c57e9969210fc9614672d2d8e3f1348169e45cf984fda7f3a77277c3bf1b4fcaa9d18fc7d104061a79c05cda510c38fc21cfd8f68499e4ae9d81ee27234442c0108653216558cb8f81ca6ecf3846ef85d5f90fc5fb9900087677de698712fc69f26b39c4ed28b97ff585c73c39cb4a86b85605261fa0640a33b0baeed4e2223f0ced9d83666a8382bf43421bb4fdbeb5246b8c207974efdf7f8d2fec94defed6ccc5ea2817c94bfb1d4a46220873937b91ec75d85c557abe52f8e0fc4a64c79ffc8b54e591e93a97a200230191ddaac18ef905fc0f9bc285c142289c7d8294b55dbea8ad15f2f0432effb7fb645559ac964c3327bd06a8f9c045487fdabf86cd12b5e34ad001290a5ccb1368eab8f2c1d2f33d45febb01735c1d5ae15e6a9c3d0d3b0b51281df8bd695702196246a07f2903df4b9db9e6807dae1c0cfa79fcb83e0de8f4931cbf8cb85a65c8861b3b25edf04f295c2a7a25694beeef896e7a680f99130a8b376e3437aa6bcdfd429bbb65010e65bd47076ff9c50d955a60c3979f2f9dd69175760fc172b6fd9cb464d9eecd598ccb6b9e5a1c4c9b780dc5ce47b8f2dcb9dfce4f7cbba97dbe642d4f8088edde9553f0df0c5cdf80da6fc25be3025a4e5aaf9df6e377895043cbe0373e787977f73b9033543a3290076068ff86f68cb78c79bcb1ba0bb03454f800622b83bbc8f0ee3a9a1d62f65a19bfcc615457f6a4b6eb6fc502dedec439df46b3c5076b8c839cf642fdbe1132fa23553cd845423a84d1711d58cf951b5d2946dcdd1ff075de4de27e3c214603b50075b163044ce9556c27f57067e748695cc9ac842e53dfe46ad0ca68484ed7fd8763224b0cfb799a6e823d1476fededa66e666fd1a81f66945376c60d1473809f98f5a775e5f2843b8dc861c762a6ea4064d677f808528db338b173729a775775e7c6853e0293a15f5951479a37a75e098644515d3bf7249ca4b57e372bd5f553a95697038de3146c7ee34ed1e738e0eefce67ca434c84beb26b83b14e6f9deeccdb5ba104dc99e9aa46e2a893202eacedaa789d863de835470b973949f5d81732236f91dfdf944756477eed28d138831c3ea23dc1db3f70d92e43dab245177e69bc84fc1935cd5813fe84496cc86aafb225325a5bd45b3c1ababdac80d5e86cdadec1becbff3b5b8314ef3d6c9d3b7c0b317a8fc0560444fd6587af82b83b5c97b28f37eaaa98d1f3d460b73942aa9ba28f25eca02c7dde408db1c97976b3804afdbf69126ebf3ace95e596db028c86452836f0bd9e1303eecfb3a24b27e9a4c6d254b8dccea78267be378fc50992f30c77823984b1c68cecb7259ae0e9da5aa392680241135ae10c0f92d0d7c9d436e377ce1d6fd8b6ad160c9f07ad9e3e82fd562c0325c9c61f8d07c60ca0fce42ec3f073bd334edf03fc397bc25703229ae80c2e4dc819f095e29c64a5ae9db16a04686d80b8ad66614abb605c0693a7a75280cd74664026dc4a6f5d880cf6a84d455b3d4c2149acf6e133434676e3c497373d76adbbbc00aa0019753aba70761d1783da4b53f434f15fc7ae3b2a07f1bc2eefb4e31911f5663f42b41cdca81e453ada9201c503e1331fc8c358ae137114108aab2a1ffd9c40b0bbfdb19e24dc03d6573359c6d435f506339077d83c3dc283a6517bf1340e885bcf5ed1e6c3324886f5014b4e69f23d23a5777b2917ab25271dd214c34f876f00a51236131ddd09e66e4f957b6087029cf62b4079d2e41e360a4c24cec4c721e494ed5f3b29d7ff49d4b291fe749bd626c8ed9ca1083e7a1b5ea703ebc3cae629c7f379839a6484fd36efa0d27b000ea10de0242595f143233068821d4f62d274681935e1d3cba477acae75dc7591836487c76e8fff2ab81bce2e7dbaf58fb7e9914a3a34136d01f25e9fc252063958b299fe937e5ebfdf070777c71f5c444e98afce9157559d6c98488112bd5e52ecec74143559c8b1c1ada44a02baa1a9b3571eeb297466ee3a19041c3cb0519551da78d9d548bf7f871446119ac9004688065bfd4abf4a556250af0c94131841e97fcac453549c5348a928327910c8150b0cd389683d99706267174e70b0f571ca84b52ef6add159ad448576dbd9bca8a3b0f14acd12895077a972eb22fbf3489731bcf54632afebab5275430c17c04c6942d6aa31a25ce6885ef0fd312b7777c77e331da6c1c8bb445c447a06d99eb26fb603932fd421ce2a2d18d11b191b4554348bac6392fbb6bfc8fd6d08e6586e7435fad99a335d69dfc730709e6a3c3e5127edb10bbaadc95db2f84e1a42e40cb75a9581cfa9c601cffbc7ea802b58bb0dfe5fd62ef5f8ce70d2e1a31e1302d38bca8fab351f3fca61270eb3c05de752b6e4983a5f97e9497dd1f3f96d51edea57bdd45a1bce555bdc15b29f186c20cf149c399476935da374bb874cb66a5b921cce295906ab0777b0325b9a32f423a7bc3ebbadbe90d441bb6193873d91b0be85a4596685bd3aff4c8dd9bd97f80f1113956349c47e3e7db77e90073b9e0c3920a380d0379b826a3e3bf265c724cf738347a6af6ca93faa30410cad8c0584ba2aff31eaa3198132b30fcb4ffd5e3619a07e9a9745be82fadabbc2a75377ff4b704f494ce56a0ccc01a312075efbc5d98cb3199f3f7e0648b1cfe7d797fb88feb770aa07f722d36f9bf5ceeaa6ea352164cfa439afb8dd78db2939c7ca90cfed937b2255845c870201789e9478a0987d4340d5966a455f39f486b5a491a593c0160188a4b0492331a45178cf76521e0bdfe02cb6a7c755a0bd391c34e6cbaee909a36dce531c8fc11003e82e0b7ed5b948c1007f5d8ca22a39297f9a479eaab66bd7ae368aacf814d02faf66a042b9a189a3ed2bce584e5c361f8e2cc4ad2ad9c731ff4f152b8c861a6686e5c117724b3fbd4abff82108f92f49aaad76534d1cd1ebada27940707064d46ec2fa38805267144f8ee1cb2d648e669d6c8b601bb324773a2bed1caea6d68a3ccacc9c0076fc04db2864d7916d9c7011f114037f3973fed5156ab8a05397a33a063324c31c8a905cd658f4d5f9dbbf7c04b403557d3415a6ae8a5deffb90d6e6ed98be9a47b6f2a7a40d1377b25825dd6d1d4a6b525a646a000313fba5b1afc947e28dadb63663f7213ed16a5f6b5cd7e0312baa83ba1f5c5f23fa88bf0f098aa6640bbcf639aed67f83ed6da0b6b31f20c24a0833a367ad36e2fc5a3eebae6f200e77cb18b0acd451631d1f8e72c28059a3b2fc27c533cb27f1a9dd30a5acf213607f90314b9d72ba30ba036c0b223a2749718dd4b869e9e4b1c6d387b2c9012621f828aa5f8210ac97e0dcbc29726e65ac8a3a6a490bdb8ee427c619852306c3ce7c72fa58333d75918069b56b9be6fac612436eadb15173dcb1f9bd43d41f70a51d27aec41d8b6862de44672753a70686e57b17957fcb6a4a334e5f94a3ae95c77c78789a539fb9adc24dfb1ec71bfe6a84542fdb0a9ff3534dbe6ba68f47809f33071b2ee608468acafd2b1f3a7fdd07f9affefee5403593eeb90bb79dd02135e9ebe5b24397146bc4cd1040d70e5ead0728014d026e162a794286d5135f3e69b021e4eca00d2ab04b0fd2df409b747924779eec44a6fea3f9b0e49651ae253d1ab629e6fe7edb2f89c6aaab9b4056d05a0e2d460dd0068499910b45cda4e6459251adbb62bf7e89a95f1cdeb40217d430e9b0d213fbfe44e924be1940442e9a9361b9e622df1aeaf40116e8d0587084b0b679636d9fddd2c9b1fcf807eeeb0d22921c5f111efe5e42c7363edb8b39e940fb24708dc9c32a67a91231e0ad9cc1177e35833f3fe7f547ce0648ead9cca98561a0301cad7d11eb46d1049c20ca5693a29fc80e68e0324d6b34c366c27706608801e1bb50373c56d325e5ee899b4291a39c20a74f963ca9b609a333540d9484a57e26bbcbecb04c5a04d914caf75bd9b1cc18aa1f18ea79678419bcee7d2fdc8bc1a3c8e24c25cbe527c58c10a67187fce8b13c35faa2acbd402d85e8213c9fdca4189c1a9dad28a1bbcfb2cb33546fbe935082b8a50e4ad87cb81368be7829465b3b0c56e31338acca17f3751e2a8d012ddb63b3fdc971272c6b5b594d726fb1eae7cd557ff7cead9497bf8ffcd40c9c386a067842dfad36b98f2bccb96da13714ee754461328b6cf5ea9b83cb6c94d6fb5e61015b346f28a7c9418781a5504ae00826184fa056821111d02d5846e9b78edaf56fec6932d6d2c21d8f7a071686f0cbbeaeb3117b9576c78c54e00da0ab5f511ab10919e353635c0439c6f5b71513d05e04d237dcb55b2bb88773e3ad5db6cf42beabea2f1f8d745bf58b8230bb97127efa7f3f28403d8443387bbdd7c391c606e9abedbe690e5e4b2e3de47bb7684b1188608ed3accaee66a27d05be7cdd20b554daade65135cdf68ea0876bbc9a5eacdeff5d542816a484b6a65a2338da8f173e6b5a434fb02faa141d3d9577347ed9dd1c6931eebcea322524a6fa140800dcfacf0eaafb07e59cc2ddabc78650e72d1c894e2c9cebd9a0f08d7115efce4b6f9e2f61b85b3bd613362eec0d2a066509ed10c863f97422f49c3a80642ec16ed4d2d6b386f856bf7574c2ae7f705a5640be2fa2d48236d0179d8b5ba7d63150a2121f37804e349af3076a85605feda47b11b96ee153c994dbebe664173dd996f6d049876a7936c309f33bf32ee8be266f16ab1bab3f2260f78a4b894e26841dd0f43b3bca06bf31cbb4f81b38e408f2cd9e44ee8c82e1adc067bdb441cd82d957262d9aead55ac844f7d6f9b60488520d5d5dca02da59233df8ae0ff779a94ffce377d87b30200646b29ca385b23d3bec850de659f18c07c65a52c850af41318d40a4856ba88aa468415672cb2f3bfa3079fcbd942ca7a3b8d1d4bd7173a0f4bac1a37a22e25dfeccce29e82800cbcb25f9fa4094456c7131618b5a7f00d83fc66d06a6940fbfec01b0b9587159f821fb3b36b48da94cca53e767e9e1b94ce4dfd7cb3fa07cd9ccbd122e1e2c6b0da46563024a6da0a706d21f98598b7cad58006893650e74b57556cbdeedb51a4f7b27be54c7c2d9a68e65824970de3bb1b3b8d3d3346c2df136c6f1f952b7a41c99b36fa978d2faa1904b3dd689e2de88d780ea118856c3aa0fd13bb865f35f86569ad386278db07d9174d71da5769447f816740314e1ae89df6bc1ace8c31ac75efb0226c598d032f209a3e9912e66bc9addf58b5bc6befc2eb68f7ebd45e5614a7ab5d9ed21bebb67c20a193d842b29c38a1fab2e58833ebc7ab430a1c43b4bb010c9b11c547012e76ae12b62b11ecfc1e0354adae7834f769f6ae2a85c642cf759acf26b5a086a6aa4937abba9b6dc0db97f2a9a71a9e56bd8c73c39a99bb4d541cc0810f3b5a7b6a1d7b53abefd03884a0d689ac9a5dfe758372e66701e7a6fa38f051729e856003dcea9b16c616a0a46beb497196ac5e21cca220de1e34d570310c207e8c817c441b7c3985f519547ca55fe130bfd159ac78acda5fd89c775eee20f81b7b28072bd7be2d69b66706aa06719485bb9ae83173f71f368ddbb11b0d6788eb3217f959ca5ba4b73a64e68cb76fdd100d8be66f0f82bcf21c23c16382f48879a144ccdbcd5c5cb9cac5f78666cba514dfd0f442e7f7ba64b3defc2033a541a4ac55deaa4f9c4271520aae058f29542ba7ea6e4777f16f8f714d928726c8b8f0bfe7778dbc45a13d8f4a049c75c14498acb71675f222c5ebbe310c8518885215c0c30e4c1dd7c4341dca7b5115716f7d643d6b402ce683c4baea2f119405c43d0489a1eb1a39c5b60fb3fed4c2f3e391adb3b21a4e0acb003c9afee8a207f109b227b8f8d1027d47966a45b8306a722b188f4a9c3ea8eeabac1edf0179ff632aa0b9d7da1c15af98c070828f3045e55cc70655e855f38b1b9658641705118b1bb827822ec39be7cdddda8f000ebda0cb7229eac79ac3d3b3e4e2436a8974c015f147e6437d39edd2add2ce0f8ed6922c76ee86f22e9df24ce289b54fd8b1ec7de2d085b93bcb66df30eb581ab849fe71a8f8248c9d133f3349fd070cb1bbbd5099738a465f3dbe1e666f2cf5bb815af6d57435ebca6a197aefa96b887ae1b336efd16d0f60d74adc801b12b900339949d17ffb484b99f815b76c597e941bc188d8c73dce8b4e09019630b84bb7c1eed71dd7ffcdf96338740fa0e38c3a5ce087d05320a572450bc60f9be1b956be7bfd82b0656b3666ef33e4e5f463ed5884c89737561e076a93166f0474d9d0e778abfe62320be5a3dbec9a15dae59e6b954ee2bfda7b7ac27f7f5b8ed612871bf46cc60813e09afccfa85973142b9df22331173fbcd7b961164dc2b60c473f264ebcffc0fd47d6a561f2c1861af2b22c536e48e9433c6247bf9b9710c8ec630eee68a2c2426c6f62d762a16f9ccf78a3cb19ee1ecaf4fb94fa2881227e0a10d32738aac374c98c2b616b215770d1b3520699aff0b94a6fb413987771a3a8f4640ba34aef1f580f7d4bcb046ce3d590d8ca809935019605e3c5c1cd697df70cfcfa993117b551cabd74a4501c30513377d0033790b7f19288ff6c6e1e3509d875c2e74a664de0dc5f9760ca5cf07b91fac089c0952b2a385193550a22dab1f07717b971b9350e7a3c3dab216044fdc63a1ffe3827464c57de949966122e93f750ede5d6c5cb232ef4bcb2d5257d288aeca2938374ba85345970ec8de5486c01960a81c1bbf99e16c6b0159a320ce364524b1ceda394dd99ec57db30b565feeb12d8a59e05b20d27fb6d6d72e99994ef9917ba17463c3bbaf8aac6362ca5d8a9b532d1297130877534466db5076f0216ffc99640f7fd133cd57889c18a0dc96eebd7d83666a49903e79dc263e1f01ef87b098bf7d62166cfe5da411c4108eee4ba4d4592d738d255aa0d239f3a19b336d08472eb35cb077a4791607da72ea6fa4444054278f97305c31501d6d3ec62572d26cbc9108fd29871617532893e4ebb36b70e043b81435320f34fd0604924b885dc9e3d91df478d99873f34f4b5c87f801df7af9076e3649c46054929549f5d3178f73eb94d99aa930e0bd39344da86425c17316219892f40de9fcc111a4e53ad3df04ff93b556f8df939dcecf0796f117242bd17207fa6c6d263119ccbcc61facbceb726935581c418a8ee3b2467f2cacdbdb86d1b6c5d3510dcdbb40fa578e40f8f868401cafb510af478bc03ba0b3d83bea60c3a2dfb058e94c45428d8ca400f99849cc4622d0c661ca0c5f05a888f798ffb2ce16ff587193e9dc85e21f58e0dc4e65b1c5ccf642ea02d464d7010f763903b224cb5b7f6b8b3f1724cf40187e1f148e6bdd0ca43b89f9b36f0c35b075b72fbdf8bcc676d9f70e66bc3f8fd69c90b7cc445e0ac72f0e521e40ab1e3300a17656fb284b0e7e020c4dd8c5cf4d77178056917c27b5fb33243c273700a4fda71e6ff2b548e5c0cfbb336a970b28fff2e710632c795a450921608fc42108bfeed10b79e853ae21be00f59d2e1c36f0e780d5b361b3a5b9a61b6acacecc6b32e5bc1334d97ee51de83d44cda70e4eec0d8553b840df612208c59e77517c556b63072c0504964cec4d884ab1d44135da69b826fa7d4e6bc933786e75d8b8194e5bc3a995737c1d3ca7933658caf9eb87a11a81fd94d6f53ce9cb8fba9194104c78a4291902cc185a6f540fba631d970c0f12eabc48c3c73f8a9e0171a3cdb18ca514bb590ba8794e3a7af35ca16a1998b3e7a339c469c70d42984e04a8a6a340f15160a279d939265e9b29effff76d519900e866358af705203ddf9925d507863992fe3699e7403798ca88ebefb3e7a2be8510b4e057fcf177f224eda69cf8c5b9b7294938b066199edd569c368a4ce7984b6a2c4843a3b46b4b60c0673cd65b17798445a0b1b218d0e769c4f630b9097571c3e520fcd300675fc5da73c7647f3dbc9afaa9e3ee55de84c74099cd2e75811fa922df7cb84c862bc09106fb4fee4d20e9b2a858ab3137c921b788d56ea85cb1944f0a15fc0fa5ef62d607d8ae9312bfcbc9256259532cc8a0de5572258182b2fce5c505a7d33f0ea124cc2b8cccd2a81f0ce98b275426c224ee5bc9668e139d4931930897bdb09221e702662e91a5790147ec682667553653952badf1a487546bc501319ed10b583f6d8b50c7894e9f09c1c7f1f3e3d74d39657b301880b985361692c327f0d2e41d74bbc9eecc68cf470b4555ec84d1b95891950e16d30fe8691edcbb485cdfdebefc0f0135982ffa290cb27454040634c4bc9667f25bcee0308edac2ba9fdf328244cc0e44a3a3a0f254d823c46018dd40223db0481043471250ed4de1e14f40a2185c7581cd4e4fe7d7b0dc0b34497e755d32c870c22c70065603e33e81a5b611db3eef2e07d28197ccfbf77a148f2099d5b1b60b72b5ca4c59328799640d0c5ce64be2d37555043eac57b922d1d8dec3f04cc6e7f2a251d4f6f803202df30662b6a907da6220cb4065a59751eca9344a8740b54b851e710762fcd4ed6e507833cc4c91368e014025b23a1488093e26587603b7cc483a02ce5ba7514eeb2bb873a9251343d8cc35342188eea69bbaadd4646fa137269ede0998ecdc19ff20b0a5d3c1d8b98406f4dbd03fbe606f60b6372c1353ce40ae6f1c87a26306d9f352fb287e8706aaf9852119080b26ee6ce168e3766c2acc076dd06dcd18c783d2007250b4c9cf6bb9a430ef014934acde48b64bfdbc7378ce81b3a9334519972d95bad65373f9eeda37f1e008a292e8a518cded91a04b082346093078880294843734f466a6db7b3a71dd4d0dec391679372a29f2b8f4ac3083fe7a97395f2112b73bb9c2506e37b8ec724dd53f69eb50341267dec89d31274260745911776ac1d93dc7efd198f55f9bec503762fff93b352bea1b00a3285bbd90c17bf3b48ded0576c3446d4c7eb3d3131990126100f041e07c33e9be5cbe3a064dd11f0548f03f0b84fbf856e5c0f9838d5e3262d7daa31ac70e5d63149cd7e97933498e70648466e166d4c3471831b046c0130e9cb21dbd799062f30880b9ce0df100b8e6b21f9109e342ec0164160eabd9f723d5713bad1a2ef793459627bb0b7c56a21ded0a6026b1118d41a5a8ad14863b704c9440ba3c5fdd9e580ee8f17ba665e2a130f79363749f829dcf6d9a13095e3e618e193e4585ca67fce1ee9a690d7b15ec0d7f6d040027373db908f2a3498d872d34fab8128f8d563074ba0630e5688935a50753a2c378bddd927609b9aef24efdf5e5025f9970b4a49e293af2137a20dcba94f08f419f126b763eb5d8c67886af3a3f3787fd1b50d9a2623f66ffd2f23e1a9ccde5f203ede6bb1becf4a1a89071b5bb3d65613054872c09aaf6e73ece29bffe5b7ed48ae454b656ebdf3d88f23f6fb5d6caac04d1882bdddc790611e6ed17a0cf7753f20bb2214772bd65cf97e7793bbc30705947fb9abcacb1f81754d6a967cb6faa9ef5b1f25a0df01df68dc6c532ef56cd596b38d937035772eb97a34c41fec71cfa394815984d652864fa90681ee51b81b098a11434d90f9bdfb48c21b65b9966f5564208cbc2968fdaa904767f8d854b60a20d80141595b10a088723ee153eecc28bd345aff03a1258b8e9557860150093c8a5cfd839a42ce204f8cd2fd829b854704ee97d862f7dc62dcb2d61937072f9870236d2db75f3e0b13d3ca2747a27b83485037fd543eb189f6e986b74aebd379477e090ba7fe23893e72a583f7179cc6c89c18978f286692bcfe6df0eba8c2487fbc801d9c9bc03ab7c0031d972c217a798c97f83e6deebadd4fd709ebc12586bae2a9c47829c1ea1c9bb578886b5cd0e0fdbad7538f05341fc9f23320464e4ea1c36d5ef0cf7cf1632b3f43cd612f9639e4e9e40b85d076431a1f34ed9b2cdc47aa3008a050af58c14a955bf5e42cff7e3e311244d1290b49bb6107871f672bca64c47f120b438b54ec23464dc2f8c327dbbb45434ba3b382650bb1d2ed02bdec188b0b7b8656b93db52c9eecb42ebfdd995335a6fd3bd0cd7c55c46b01f0f0a7740e93e8b9bc6bcde581d874a0c481bf00072c0a022c97d98b58c84886989980f45b4cbb0fc133d51a940fcb657539171957a4b373c96ab0b0145202828f3977bef7c8ff5434d56f152133c5e1d223983b7494758afacefed4544e388b7657706731ea2bbac0f816540ea5e52e5abddd5d3832068dd396bb7dceb9ebfe97d87f0e83e9a0994cde615eced1b1946d67eda628cd252ea0985a5a89843b68c5424e542fd232518b9209a0bba1710890535572272ebcdaa748145d9e4608e41dc39737b166c39862b37ad0126d0c09e24f43490c95a6b6108b486111643ecbc1d85c1d4513e6b64df501cf0c7258fe12bd5684686df9e20604647b8a210481df81c0c0bb794571617f6d9cbbf5bb40b6a795f46c194b0b6ec3e9bd890fb3767044461ce1eba5edcf25040a4dd0cd3bd6de43722ef2932fa4ffc942d2e1f27ac982918c3c9c60374830028c31b4f03febf0aa6a0b0f755c60c487feb6117b12c84b78c0c1e368ef1cef5d07e4151b96627a4595a81038fa7e6faaf552a294eb885a442f99b1cd010bba4d88983a1e97414117e6efc1c076ade9fdb7ed5857c8e16cd2893ce846914a5ef87140da4584f56ce6f9a794f948f37de0372c24f1284ba0d4f7cbff456e68a4ad281f7877d79b2b769ea37371dfc3fdd88b76e8a94f8951795aa7c659fdd393b90608a9fc20a006ab935791f73d51b6fc5023f959f6ffc19e01f589e88069c0a2a3f0d3f073fd776109b162fa042d92cdce04bb1e913ebf117f073b5ff0802c85321bd16ce6ef4e0a1d65dfaa41ef43a9988427b98ad536e7ef990f45d5f68e7e244dbe68de06cadfca721f40f351e22407a103c4d88232b314fd88f4cf74be8861edf8227e95695cb5958ff30de78921f67bb5ebb5a677bf5dcdbffddc31e11e1d24b3b5c1b57f5b9e1e28faf3d718c476dc10744711cb88a6f8f3529a0e4ab81190c82fc251bdd3a63a878d8eecfee3177db7a0e938b5b6d6afb7da1331a361f3b0f70e0f5f917d0ee90c5f53964d9d9b272af4cfacc123783867c53b688555eb268b20b5c84a966d9950d333ef8d619466b9a33dd66cc29c7eae736e3e635ae55fe0e721a4ead3a6f2049de47c267185fc720d46dacbfaf8d1ddb410152734add9e6dbdde993ed3a3692af6f978adcd2a7f1c11f1ef54442ad17a5027f76fdf2719cdf57882403cda5acfcc45247b9ddbb6fb3ebac6b8d689392d8ed211ab1f3b6316192bd3bc5374a317f43d1abfa22f9ab3629f6ed849ace5fbe76b826a647ad7b2b6d186058e681aa46f0c65fe77f8aa412ab85443298d8754e8a54b7b7b3e8a0bd15986bcb5352bfd3de2c046eff1952759a5373d43f19b3d8f2ba6d25462f5834a9b975375fe24d8596dafa0346e4c0890f66de522f4559a925ea5fc3dfe7b54ac6d97eabf1c20a41b893aa9ede58e80134e3fcd90b465d590b57ef19e2df169fe657915853505b405317e199b7f00dd9486b39f3d98a745d399fbc6abcfc90d36d2fa7bce9cbfdb1aac2b6c55f4d9ad7b958530293874cfb008de89b456cf598f0d73b20c9d1a7228a773366a94df83d1b2a5d9bbb8cadc44e47587bf73dedd6de42a0570ffd4583133ea161457f3cc009d266c44e671647f8b7e9b629b64f26893cb910b7278e817af547c87486481529cdbb7cc6efb79352919cf87d9d348798ee7664a576161fde49a651a8730b83a19c504f1d61177642a32f01a4da386f8476aa6f904df80d9af1cfa382fabc75eed61b72cb928dd4c2a0250da55b38dd4505dfdf2fc583852787c081f302d1794b3eb212b59635ce0bead5e4c1b20d88ddf6b969b0facced21dd09df4d14df4cedd08493c858e334b14d84bc7763209efbaa34dfb1f35a04412bd0c71f93a9ebbf12d9702c66e631cec35d4cf51699ffdd2742df0ad9e86dca6e0efc10078ff0b72a6ee6566529801e3a1f91c0fad0040d0347bd3dcae827b6140cbe11f7e7ad87a3c06f6c222aecac2fd6a07d88f5aae217fa259a72c4ca7f29d3411a98d6dbd92e203f3ebdc32d226945cecaed8ef59b9be19f6f42457e146c2f0c83923bc163e46ad2dbd3259fd7d0e434a60d85e6e6f53f3f2907f761a1d178d170ddd0cbf3a38d0d6cd5e780c193a0be57a5fa083d5c20023cab6aff94c7437e44f1e6405a2dddfba4479750e07a12536998c4019e512109ddc9ef505ecfa72dfa4ad4b13bda76a97dc4d02dc5dfddbe5de3dae8d49c0842b1f31b900ecad06b29b452d6d5eb176207f6e8907e97b6509cb4e3ae21cb03123fbbccd3247b94d9be9aa7d96e09be049265a3feb46b0af2a616d6c9c8fe7a828217dbf292dc8721e6ff00fffb9663058555a9ea14fa2bc5c3b2889ccc05f2e7cd8ce227667b91f3c45b2264feb0c9886e32f8dfe843a771ede012b69fd019803024083e2151d48dfb4763bc489b81fd1690d43e411de9fab1127ed89678fb2c3b7fd2e9b31f5ae86b5c40ca9128f07b2947b683f70b004d60ef782832bb71ac22761db267df9e68499e6572e0e8ae79f8b6eddfbe2a202a161e33edaa816242d6ac3f1ee0ab1bd05b59c5465648679b4738a308c86e085034c098d3ef7172f757c751c9fc72879f1fdc04095cca4fd39592789271603812fa2d5b57d14eb1a6fa0fa7308571a39fa3f05ec80acf819c873ad525df9a03d1f867d8f74a592d6e1ff5272157efbccd498b72dda4154d6de12eb1c325fe16754dce62610ae19edd8cb4dafdb38cfc65edbcb8b696c4f95072e4193eaa2226b834ae31ef4d0c0b1850b80a58e3844958c6ef4b38279791606515f55fe20f6bd803de2698f6d6feae19aae4d93f0c2a02d210059543afdf32a0370d6a71a6196eedf80c9808b955c30f88ccefd404e4ff6f1036a5be3d5bf171c6842c3b5929b20b0ec9859abfbfa69bfa284dd978079c4547dff3d3ef9ff8441f8ca1ced84627295fb8864f8201cc5866bd00f5a903d01c20f70a6e906f64c03ddb8daadc89cb159b3b3646bc451e5208c4ce2c2ab08aa19955eac7c11094b61aec410afcec7bb2ae20be88e24a50456aad87f8a42ffe399dd7e197210f2f97fb1ac358a2e753207abbf49a1ef5ccf4e0fd0fc0f3c94a7f7edca36bab75ed6e752cd4a72e5221f2a7f8c28f21f1dad862ebd4e7c0478ee1f7a087a709476121ee678fe3009a9f29d0ff7a067caf07c2fd0be41dde9b2e2c52353325b4db798bd914700f918d6c32c1278db0034e6175dce3f402993324537b2e03be6c59c56a926a0c343e0cc2c433dd6ed1232816555afd8d9626bde84220d94e02c9a5251a8c995eb904234cf7a7f8583c5a31012e8791d03ef388d3761c82fb0b7a58b900b4c562b4a71eb375145b3ca64a5bd3cf962c8b975615932a47e28c0e1e8cba2c39a56d976f35e8036afa5da2fce99e6258b1536c083c90e0c13e826708830f74106171e6993fdb6ba33f2c4236687d06f934fc3a327a367e95aa6f0f8e65e6cb5ecbc7d5b9490be1ccf6296ce1d737b475495bc46c6c5f8dd9b9cd9b1d7f50fd28c284036a983ef848954b3472a775d91fdac923451878fe91d3c12076f318e23c6ae495d259d3170409ef6b2253de305ac234c6e880ccfd204b481412e376455e78e0fe15ed0a900f64fe55b72901588efac596c82d2ca3c2aca517e500e1b23aa9cea8b3af12125845ad9a71718e8acd53f587b354c987030de590323b542732494f1eea11c4bf09bc59cfa49af15eab6696c70c90df66f7ec488c02f43d183fbb60d6c42369a51f07b65f52304ccf0b4d80a766ba8db3d53d24d1707179d4382de1bd727b038e2d54e583eb0568984a8193a19bc0a58af3ebddadbfacad2f812853ec0c063c3b8fafd96d16714b45bb7ec162ad182811e1f2a64fe826830a9d686cf7453e56923530060e62420a343f1d75dcd1dd0359a967bb8a4b50ca212b70f7ce93743ba014ccd7d99fba0dee6606a1fc2a21c100fd2db3fd275486a52a014a1f0cf74bd7a1fe32e1bcc9e2690cf3adb3fc9d9277a64fc38384fa00d9eee72eba8323a5b1ed8705eafee985973ecf60fe51202634b41cac26493e2bab462000a76785f38368b2e107da77eac5e46a5bb4699f7d4ded25005f48cb7e7bb9c9890a2d2d367e2b484c5fc65aa3b9c96badcc42e1fc2bda51526c7a710cf77115494b468a200d67a40cefcf8acb0e7d7a9a664e194b640c256b0c9bc8837fe2e96199e49aa002fec3da848ef00f49ac00b5d60f9e841be391ac1772ba36fa2810459c40eff8a4e30a4bbe563ca81af27820d8b1f706a55a3064820688a47033db08cd4f4dbb3618defae88e5795a3f3dd065ef7d63eae566f1b9ed7a0f70793d639de9214793d7846c53e1e7d771a7b00ac726d8ea47edad1ea00e496a02fefda58752593a7858a5c053f54790c3e8c9bc12127b0900eb18687ef03a1c988b8d4a17b4ef835219e75375897365d96b2cab52a3205ee9c8a2fab98cee803210c96ec0ba5eb75bdc7230e0eb9f839e050d5e1749684edec85c44387bdaa9c2c7781f0af89a76fae3d77e06540e80778f28970e001605074048098945f491c17b9f214a8759891e59086463cdc3520936bac043cd0f84e81efff1a7027b5674b8c561983f38a0475af65e3786b6eaa82661bc1f1db7f85350ca38eb2560cfa195f56aee6cf3e0b9806e81fb88f6e3dd5a62338e98271aa1cdab4dc32b567df90486091b47bba4043428ca314c795e2dce9507efae44c11f6a663fec4350fe55085adae0ad3c26f2519ffa102663984709d9a8b65606711fa012b47717afc664e6e51988ebbd98837e60c0a8adc7af4484527a6f44f8cd82db2352e705386003e2459b808a98500ae4beadbfc4a505192380e1004abc8019a9883a0eada09f7fcdb349b2cddad2560433d032d1675aebb20216831cffb29ea9021b4dcc40968b5df1281614f06d0c7fc28b50049a34d239b3e4430a4fa970000af8aac925734572409d87d15d633ce5ace38038cc6cb345bdad87816aa669f6b5c13b0c37da3fdd76c19ef09f5e4be4ac2e4aaa8bd028261de630a3076f8775ab1ff7a947cca659f7c6b84d08e050b8059db1850e3265e4f3f914f8bfdb14c1eda874dcdcc1d22532bb57d4fa3079db678f572a9dfbebca979f0e4dd36b3a5145d3ba5da082caa401a8636950fda65c6c35e5265e6c5e3e2b011a735c0240895eca28d81e0988e4948738c0f013d5ee5752392e6816def22350495b936c84b653febc7507684d93a3b80fbba53cbd283b9ccd1491dbc41b6d2b57f28d97a176fde8fb3069aeca915661d0a0652e8002c073112f93732d07401af4e05943dce3b079e7ca48a4c140088ed1933d059c7528f8af7385f001eb24cd0a1319d6ef740379156b83966ab73338e9ea5eeb05298864d4c0b1679fdfaca17e94b44b8f63d3d20999e3bb8a43e27c7fb17d62a08fdf440f5f748b0cb027f6d9918c0c626faa0638c15551b10f5bcda98f1ceec1bdc7fe66d07caa8f75ebbf310b7d6f6328e6987c0f11ebb5fa8fc9ccf16dceae90bee4cf735f1bd488d16fb0080c6ad786396e09f6411df2dba88ed890a9be9cb08aac6a9cc31fef31391b7a8b4289780193749664e40462160f70441fec5c6d89c54840c499e14acee9e09b39188c861d478492d0f0521bd0dd236ee8b5a40c90b4f9bbf4a372130ecda37a42c5b06aee2e7ace4408ef15f107027841ee4119836ea79a6fa7aee9115b93952ff43119a311bfa8cebd56b363e0626742e2cccb85e9ecf9a7f7f9ed6fdd9c01f7eeb0f94edc38b697702b7f86a4ca44b5a5117844a5db2e3c40ac7351eed9f38381e22277607f50d5b4ed68e0bf298cf29dceac9813a7e46d4cffa734556ff460eb67d58edf27e554c0a88925eb93a8a405f6cb08bd71c4ca242f86627614917a45e4f7d050d2bcd8564fd9fb21b51055126d2ebd7cbce30bbb3e94d56c07386e290d3fe11e6e355e981f1c7ac088f303b89ebe17ea62e7d3c89bc6f2e89b67c8d4ff429e41b80b4a6922e1f3fa022306afe3990747e1ac8d30b84e5b78eb58c2fd332c7bf3c715fe1fe16eac3d3fe7bf4ee6178be9c9d12afb7e2622315a4aecc6e758094ce0ccf30d317290cf43acc3439d7307d65ad942787925b687ab6a8e64e90c335412c5ef0aa212d0ef3987fda7e08be228d8460d1b2589eaa53b0569a3d811502b15b5421b0d75ccaf57e45ffcf8e302da623d84f5cf71a9f5580b12823abdba68420a5b901301200e03e15fb8af1e0a7c3dff1df0435b7f996da225571a6e6e28da00e6174c735f244219101514c88a6041168a0d552fb0112a608e5afe4a4d100b724ded811a9a886ff67f588ddd78bf38958322e9567ac18164d11139ff80e58341beff18e23faecef1d77c636ef2c4b53b4b135c0785cfbf8f90f606256dc9eabcac8f944aa868fc741e3377dee41ae12bc14cbef0307140e0f17a3ea5b1137c10f1b0876dfffdb0f32aff150dc9d39282d9c4d467e0ca14a1d8c3af99817408dc971eb0e3dd079cf4bf563ce1733f9923594ebcdc5eff9b209675429750f123f5a54b8c0dd2a7a7be9f12e9b92704b69927bf4fd71ec42ef48ad165cd9912d5eecc10be0303b9dc55d5eeb8279c96d938e49f5675dd6d8905683bf8f883503e8f575af5f3b85e1921d34525421b33208d878956e11bebc5a25de8e17d5f94621c8b7a20c7c7796ce658260c15911f85c73de4f6cf6da7e8cc89354c8d9a839bcf1936f5cf8a6654134e726d6876c2a621d4f8ed729042f2dc637184e811471f4ee09331c5fad1b3688c43904c4bdbbcc38079674d2fcecde9a6fd1aeb4e8ce2867315ac6281c03b4097e88ac3a12b9a5e127ea321ae94ed232ba90dbce7d26b0d7852b80716d2800e34a373d9306c9eca0bf3ecb1b7af44aa45a85803a182af2e321f09457bb55cbad0d375b0b17ec20f98b65eaf40bfcdbbe2b8031811b689d17d719390bf973f56a7d8ea77716c74248ee2abbb3db47581193b2c06651d4e121f4cd25a829056c4439afadfd1753f81c186066273938cd42dbb038ceb4f44dabfeb6b47d84c848c1a945f3932335703b87bda8ec135c5f85ab0b7bf571380835f12c34bef5bfad0c91de1bd57634bf4910f089862d9b724e8d25b5a5469336d25cfefb99429f186b4c184263bf1f48799c6a94e0e889babc475e7069760827e661dcb212ea3c011209308e5036f55f69ee799391c0694f362f3dcf8a3c5a7d99500788c013938119c7a1f445f0891c3f4dd474ce173432b78db066e7c2efc5d0d246bf530c312883eb99df7297e7ca28b4486ee08e5ef30b279208498704748d0e38e10687141f0d1f9081253c6cecf1fdfa0b2fdee1274cfe334275cde3a41011b34ba80acdc1a0d4b9a4090d5c2cfd2bd1eb97e324a20d644f08ae942883b2ec607fd0b3c99be57f33f107b3fad4bf708f9b7f47b74d87e4312652c29037586b4b8cf224d695257c767ca2d7b1d00ae36a0585d9e0a50ec7176866ab42bb41ad73522f2e39804f94d8194564eab123ccfb8a6a9899fa660a791989d8fced2f1fc0388cba6fe1370abb42d635bfd5930bfba52979be01f9e1606eeb088c77eece7657096410af87440b80ea823fc630a5f335fa677f5225159905688f0272c66e00a1bfcc553de04e5015fb4c4e63563ba3026bc056f36d100f551324b3879c32179e1fccb0e75471377775c19d0c9c2ea2235b9eb61de4192ea34a4e84ec94cb58c1b2df67584701deb55ab20169b8660fb0aea96750e492fda93462b787c686014867e2d11a08c04c85d4bd9cd5c2b1b50be0be398fa0b423a7aa1024f37194bf7e7f68c00a446fa749a48e5f0108294dfc6247ef94c8070803b89c2eb8c7da2838f6428a3fdf8da2b85c3e32aacdd98b7db4340ccb1978fde0de95ac2bacc7ee5ff32d82c12b69b0f3e07227c82551a6aa96bf19a48fed1ea461f4d44ff27c04f846c3b5a3e81beae7c980e695a1a52baecb4d70dee370a1eb287c8cefb8807b2caf523c95d97a4bcd42dc828e41bbc9273d0b3153bab91b2b195d82b4cbf7bc26d3c4929f7a2116d275c23a4db332d96918b8c4c196087f49feaba1f4bf7682b27a8b5290b66955a560730dff498f8ae17ac102c63645323e713f3da9b947329f50ffbf74f4df5b19f0fd18177e9bc7cb643fb12681ccfce90bb5d49be2b99532c7a4d3a0c891748b3ab9cfcdff41b6cdfffe53912b56468d27e2c9ce2fe08ed085b6c9bff11a90f5ebcfb1753e5ff0903f19761364422d4745f93399895579a9574642d0be5503c79e39ac525db8a43aa326b6812f8569188ce2887a36a8ede141f33488359d14eab7ae8fc7b5d3d5e036e8b77c17d0c616d2f573959b025bfe4c3a63281f259f1b68d66eefaa5b2e22bbef23c493a8f382faaabfc23c1ef4dcb3522b401f1f33d78233c9feb0c289a82b3c12652eb9a8cc55512d2546200fe3473a84ccfeb252bd8be2ce819e2d6d84c96bcd4c38fe1ecbe22ed72cf9233dc6e3790fa64ca36192b419af4284f38f8b84e26ae0bc7a3dac30bae2e46f83f108d8ebe480d3d5b9c2fb721ff8080f647aa1f2d66d8c5b5a6c2b4b6e2efcd67fe9031efdf4e3abe065296b3d3c0085bb637117be75dd607a59b90e37927a28f6f588c8390508abfc0dd651fb372b452f3205b760b04746162e24a63c0f923871ca84972840a26dad692f28c555d2cc7e2bd6757222532c564d2abbad134f9ad74b13f766a16a6d519f891f1327ca5c936b86a212cd8c486d8e021f323764f8f12ffb3a636d8f6b18dfd8afab6fb9e92f3e6366a330123cfeeebf8fe49a4cdeb61e240ff13a0f22606b8fbc4f0aa71c4e67f8c7f433da937eabebb81bb3b8bca4ca4c7db511bee48f7b2fd65eda386093b6e9773f31bbb5d72ef0381de285f44f170cc3da0b35731dbb5713f4704e30d5c2171f697afb1ae284b4ece5c7a87ef71cf8e1115ab6c177655e9a09a7c02574783dcc54eab5c3750b2605c19cb8e23adc7562c7a50dd8ec1e2d9af79996a63774dc405705da9d81306e425da68f4f8ee66a12e7f5222fe791d37ee00c6c95ffc314879148cbaa3e8e320c463bbfa5704ed648965f6995c3acc5f42e7ff53f2d453f3f69f192b3079f818795206b3b11df58ee3d73af915a49a5d2bc218b0821cfa0a37a97c1377a51f6fdccb01a5a38bd2299e7ef44b309ca463c102090daeb76f1655236fd138a76eb3d056491acff99078893a42aa0db3a50ed402411d46323e80c0b5e7390997038f1ef3579b373e4a215c8584be5ecb34c9ac435e65ccc71d2923f219438aff32180335efba48b4b9a11a09eefe1fa2c0cf0f644d6e5b5efa912f42661904a157b4d82a616171b188d0f95a6f29ae6a75ca9464683a7ee55ba28e10a755f68a0774def914bce2f9ac7078173cf2b239c219e0f65e71149499023ebcc44c0c3c60ae26e900c3300cc3300cc3300cc33074c4a0f97ffb0aaa0999929cdc43468ed091755d5e534a52269992e0ccc4979ece4c7c3a33f139ffffff91ddea0b7c0c8c0c076179e94b45445270f5aead0e7bde9a4324ccc65d868e314482d04ddbddd01739964224f758ba98bd62e972234492c5b0954b1b6db55225c8918048c4bf04393a12c904800b600c2249c78f2a9ff9f2553709f8b0f181800f0960e1050c4124476df34b42cadc7c1cc28319f061e3238b0f2db0f888442211c445a40cc40ee222b1002310c9d933764e13516f970b49cb9170ec87165744223c6ee8e8828b2e6000228b1ddb6365b977ac70b3aed0a4322dde9821f921010a7c7c48008b48a48c201221418e1b0bb891231221418e1b0964a900e30f49b536f29352f261730b49fbd0e28a8f08053e6c7c340087172115405c1c00d400861f92b47c3484a98ea7fed487c4d26ad19cd4759af990944598d97a68bf94827b48d43cf25cffc3c9b49e1e12fdaac23f3f95c5479793596c01b203461e924e88ee533206f129966e01030f497a2f6679cea8b8e778ee90149336d1d9fba1e3ba2b76482e7539c237bfa4e61e92c7bc805187247d516b29c65ac89047229148064c165b8080c0a043526511954dff7c45ffcc21d933cd3d07e149ef5a42d2ce0160c82131bec6db86be0fd965485ab1e45c01230e49b9dfacf23cd4dddb2f7a7b78a10347030c0260c021b1359338351f9f22436f48aabfea17cda3b7977543a26b0a72c388bbeaaa6d48aada1c83d4bdbf90230c362459baa7931522173dcb8103640d495a64deef79ce20ba1a1264e68eab5bdae3c26948eeca146f62a3a4bd684872f98c39f62b6fd73324ef27711daf6be163cc90182ee85ca63497c658860495348af6d6b30c3a32246b6652d2c365ceddc6906c5632643f5c6c4c25312408f5789f37ac66f33024a5f193bab94bd4074352f8ffdf5ce5bf687f2141c664a9ac3e95ccf65e4874cd1a7e83b6d454ea42e25ed89293e184facf85041f0db275664c4fb885e411a57b745ce7ff722d2496b69caad1475948f64af576116a2c24fdbe76eeb46f41db7d85045dbe95f543580e765b2131e5e4a26dd55263b8ab90f81ee61ac4aee96b9b0a895521e28212e9a6624f2141e4cfef2c97e50c5b0a495e4acc4c27d32caa8e427238db8caab9629590a190b82174c69db77958f8094959a2972a67c37c6a9d902c9ff9ae479caaabb609c9611e4586093121c1cd844ee9d799b95d4272386fd3f334632aa34a48ce6cb59552df87bf68121236bba66db9efa620242485d2b70c6e9d59c7232486d1cdd6d93c9fbed20849bb1994a8e7f591a92c4252e5187b3ceb8708c9fb394675a896ca9633842495829eb28b1d2124fde914eb4d3ae85f320809b3d12dc85e35d1d60021613d3da894e436c8d5fc20f947e5ba11a74d69d5f820b12e6ae80d16e376d2f42029bec88ed174429fc6f02071a4a57c9774c62463cc0e122daa2825ac4c3a48cc8bd62d9682b434e520e1e45874139b948a493848d2af34ddf4fcea166f909c7afb662a9dccddd006891b355b949329c5e0d720612f77bbeffabfb53448ce59ccc4e8b5cc20c94505d5a192bba74c914162899f8db5a71824fdb6a739b94e1e8241f2a9cc9c936c66aaf40b92cf7f4b9d34eb4dbb20c1536bfe4d328c16246fec73919e1a359d30589020f62abc8f1891f5dd2a12e37f4c27478b9865a78a6411fdfe759e56749e8aa4ec182ecca7d2a7332a9277376c57a74e95d74e91e81ebe2ee77535ad99291274dd69287929ee95aa1449a74f28194f64a8ca9222592ddf59e8b5ec54b25124073df55d59af427644911c847f586b2514c93997caf795773c0914c9315784febbbbcbe3f81b20e847b086de73200302a296c516200bb0e21389a9c33cad099952c61b7c010484159e48f6af5c42a7953a91989741a7b2e4a2f33b2712e5f754da58de269234ae99a69841ee6f0a0708ca911fc0c1c5170a38a782b04213c9651927ac742c13c9e97b2a54a89c4c4dbfa30788d9ebf8420b341e00088415984814297f6b5953fda9a04b246f9d8cf6b96d46b36889049d4a87160b2d246d0421d073082b2a91a4b5e683584ed1635e4a24e5afc6defe7ecf519944629996521ee6d5ff11840024f1df0041bbc003cc05200f564822293db626993bc6fc4e8a4452be117a93fa1883e710249282101bcc36d4cdeb472479c79ae76b7665b71d91246ff93a69ad0b3e6e2392b2bcfac49f8c419e8c48ec30da32ece2c53db9880421734a2fe61bf25adb1b8c63470434ed004014562822f9448474932aba54502292d4d7e575fe1caf5a4424a58f9f29a78d29ad7842d24072b876c08a4324e6cdf2a343e88a9e6388242b338be9d229a1635c88c43b99b19da5a774ac42d2fe03fe1fd8f15d98cb61052112b4ace6c78659101e3488041d634a5aa184acfd2c8804cf0f1f4447bb701986a49d644520123fc35bea6ced523b024492d04aaa6379fc87e498f1a1fa3c5ec6510949bbc1df830748086e78d1c63eb4b8420b2c3ef6adf04362c5a8ac393d6f2a4e7d480a0fbd2c51bd29899c0f494a9452225d6f84ca7c0f499553cb5d53ebce8ef49018cb64d5c4f56427390f09c26d3c8ae5b1a0f38a87e4ccca1e192bc8ad94de21315f662625eab143e207e1c13f954a76725387c4d0d3dc1525f767a143c268395963c252c5abcc2131a7d9f9d2dba5d3c942d23e8003c4fe039ef06361e3e3021f363e1ef061e3a38717108844767c08767017e929f09e238b48241289945d009060851c92347bbc7842655c37c521c994878aa8656a2ac121e1732dc3e352eafddf90e449dc754c64b57a3724d606193ffd9ed08fb72171647d792599c46294d89014d6154ac6590b954e6b480ca2bf59eb3e5ebd490dc9174f2f3dba892837a521417a6c6aa9fb28a32634247f564fb10d17849de90c499d475787c6d11c663243c28bfb97ead69b90496548502564485319eb3698c8909c4f552dc831ada692c690581de2229aa1b27b490cc91a36bc7fc758a972290c09fac44fca49df3b2d8121b192deca78326e28ff0b4967dd1d2b8df04abe179254d0a42d8fce73b1ef42b2a512a1bc3c2e899c0b09a7cd74b79c8a6ee8b79024df3c56fed8eb68bf16124505bf3f6d1bd5f5672139a45f8aa9b01a133f16923e297da5f19d9398ff0ac939368a6d0cbf75f7ad90f419736177d9c9b35f85249d3dc71362939fd0792a2458cef4ff73298feafc14123d2e9d4cb12cf96c5e0a4996151fb3a92d8ef0a390fc21c34566de67d21e0a09aaf36ce7c7dec8ea9f903c9b496bdefec94a724272cca72083de3bf352131253253b4d0d13d79f0949f739a8ace761a4f94b480edeb9829a3ba9bb2b2159764f29fd36d1b52721299d650d563a4b9c8e84e48fa6f2d6cdc354fc0889a36288ca237473b48d90201afd3efb62e9921621e1eb53547bada94f9d0889957544ca660809d6ebb9ef62cace092139a797d166397f6a198484f7ce9d25bcec72070849af1e6331d67cd29b1f24f57abcd471991af641d2cec86dd1fa2617f62059bb65fb335d675778909c5e3bf67b58a8f576909837738d7af5105d078931cedf8e3ef5fae62039c72e26d933f94f390e923aa7e0ed7baa7b946e9034a242c9147e64850d924ee36becafb0d942c88a1a24fae6ed4a0d16df6d64050d124c8a69a79c97a66164c50c12456c380bfd8c5d17592183c456150d2ae7b31c2ab2220609e7a57dd3d4659a0fb20206899f82761f95730a4283ac7841524a32cfec59a62c1a64850b1284f6b724aa458aa5202b5a90f862bf3ba2c4c7c790152c48d2b93655f896af584800abd8539e53fd0aa922b18434e9295a44ac291589416bac0deff2573f2a92b4f79eaed1eb1dfb291253fbf53573532475c7b9eb5a121eef522477abbaa82d1d744e9322e1d3dae2977e2d937b14492e22daaf671bb46d5124a9d5a7d0b90e45e2e9b0bb939fd5a4daa04812729643965042d9d99f48d29bb152bf527930db1389419af8f9674e297aba138971af9f266447119ee644d28758da7ce7f17cd39b484a29eda75c715398d89a484aa74389cfd3cb6be94c2485b3fce8154aff7b8d89e4f5fa710dbbb494f52592e2df2e74cef2ad6a4b24dca8b1509b32fbe92b91acdd49684f7147ba881249667b722ea9791249e253b2bde441c3bc2c89a471dbce20e66299cb9148d2aa55fe29fdbcad0c89c454626389acabb4243f22f17494eeca525a4f871d91a0bdf9392baec9fd702312d544fc3f656cda0f332259335ca94e21534e0f2f22b92aa875f5ee06b9614524ac59fb75a9a6998613913473428d27d3385a64442476544da6e25a8e06b903e6973fb4c0e2e3a020230e496aebf1956a429bb864c021e9d462b45119bd224a6f489ccbddd9563659ea7024f204196e48ca271f1e3bf5065db90d4979376e3655e9745d2c39d30ac86043b295bc921fa1b2785d64ac21b95f64f251756e966342d24aa4011f366e0a32d4905ca1622ac275b7e6bac16307028e211c5ff04003c84843c2a5b84e0b4a4d6b500a490309e1e1858eb2bd42061a12b46438af0b99d3ef7c86a47e2bad1e5aa7d469a320c30c09e2fd2976082dfb6ecb90d8517436fbbc66397b6448d84e19214f0651f56a9d20630c096e17639596e7b9cd90b4341b1f957c48008b8f2cb60039810c312468dbb09ef42915a39632c290a05ad40661a64bec8238824864419020030c5f4898cf6b9d2f256155b18323b802c8f042829acc35ffcb2cb153489a0e414617d299477db23124ed4e0632b8b0fecbcccdf5ea02195b482cfdc9b4634a57aec19034906f820c2d948c2c94b2f5abe8b45752fed42cb60001400e646021312c63e7ee988485e884a495c49320e30a6b8e1b64778a69f385a4fd0e1d2039dc58dd0a645821f14c45533243fd05654722914824725676699051853d77ca5ab13aaaa5ee8ac14e5467d827adac391b1f36f4f8400615926dc745b55f8a135b4f21c9f268f9ab2ccbe9974252af78086da23a834a461492f3e9f4f9423dc8cb18a4c77fa0c75f1189ec0e644021d1a27c75f6d8d02fda2724c94a5add6d4d7735c60949992ad6a9b4fead7c13122cb5a99171cd91291392475f3e2194c5969074b3264f4366b4af78a184c4f2115a47a9d5dab64948f8dee0b1cb93ea6e90909c35749df55950a2324748109e529fb9dd9cf4abcc40861112630a96299d8cbb97531112d6dcdfb26555ac58224292989c98daec595ffb1012f37beed2a24db33c4808c9e71e573c75f0f5980e42a28a2995cf74b659110321d172446d998a7f902093e638f517c592cefa2059364707ffb3b87b393d486c3ffd9744e44d8a4880f63abed06207060c063278907c7a749215eb1099e91d248ecd5ec5128d7d2624430749e549ee997fcaa70d472291c8ee909183049577a7d72d28517fc241c2f7789f18dd603e9f1b248f0e9e4b68bbd820d9a478bd5f8c18ab5c0d12b6b26c978ca3ad7e1e2091c8bbc92cb60021810c1a2485c71a7d9d2a7b076d165b807840c60c12748efe33a7e232481895f6efc35d4b331b83e48e959577ef62e729810c182428b1cf95f77e224e7f41920ed7d571597537d31d90e182c4b46fb37cea4407437a7041026b192d48ea8ba3945063e19b4e32589098e2ee8c86c78bcbd12a12536f5e5b3d61579b7d05b430554582b8ca5815d6c2adc3a94812d2c3299d5355396ba8484e2944cfed75dcee4f919441a7cfa8be0f533aa648b61f9556bce276435b8a84df2d0f9afd4bc7a563902231ed6ad0aa72f7b1330488e02bb01a63148962a3d492967b98fc185124d6c90e9522963eef1d928627462812743c26d54b9b0b1950248dfcb74bd32986d014e40338627ca214254e43e5361187a41d4f74820431389198f459523a99eb6efb9b48506d153a6ef6b64db22692dd3f7f529edc4c24c5f6c75341638b102a26923bce7c8a33ddd9ba5c22c92dba558eb92bb9e92c91b0c1c3c47fbab2cb2c24ed8b1d3dcc74c7f7e002635422592c3eb57a558a6717f522062592b6b2b54ce78ff3f2e8f13c78200073120715d76bf552caa737043124c1d6e7f5a0524c35670deee8a12346248c69ef635d26b9c1c41e03129a250fde9eaa317d623c22c1bb4a084fca2e67d82c80021f6780188e48dc191d3b2fc598d37b2169e3e38a372012897c688185d90f6234629dd19bb289d97c311b831109a3dc66bdbf37b8e6622c22413fe9acc93c6c87ff2611431189f6322aefcdd68e25212462242231e5f9aebc5c47cb551131109198efde82fa7499bb8f0122c62112334468919df4bf5529242d3fb4b8e2e36f78037044202f8b2d40024086188648b2978b1f65d6e13b7f861885483679e2834cf94975a643d22a1089ec781e63884188a42463e944530e3a056119880d225174ffdc47fba4188248b08a8f29e8c5921733c73a10231089a552acb867a9410c40682f1e33c58bbf43d248c03b1e99187f48d00e55ae56e9346d3e24adec438b2bcec920861f9246b606cdf1ff3db512f0a105161fdc831570b810a30f8949698977795ed1a9a285187c48b24ec1f28e9e52bd5524620662e5b2d80204005788b187e420f2378ee769e91163e82139df7d2a61c1c59387b13422461e92621ccd66629f73e5080f09a253651b317ea6c3e80ec9e9314c94e98eebbbed90943f27edaef650cd751d923786ef52df613a24bcbc8f860afe9a7bce81b5d8e017345c057d2412899c81989a952cb60001802762c821a9e3eb43e7521c924b45644fedc614730687c494d35cfc5097aa93de90202a98568fd1c22f7ae8e822c111810f1b1f8e838b077cb1e3bd98c0878d8f0f1b1f23884478bc8e2f7a0c22861b123b5d760d4aa9515bd23624e85c931f57c9b2a6d4b221395f68964ae93fdac715440831d6901494de18cf31c520cb53a210430d09a7beab9e9ad7648d4240de045cf0484352eeaa323d661be2b45088818624db9c2ce7cedbdff93f43c296c8dc369fd333e966487ccdff4c1f6fe3622e030140136294214925691efcd294494178f4c0c167ec6c2006191294bfc9abd4b3a04b1d92b638628c2125539e16195378e3064722da238618123e495f3df91d192b179276628421e94aa71837c858db9e1c7923061892530e1bba69a74f5c3e24f13eb4b8c218cb620b1015c4f842d2ab75ca56665ebae237760831bc90d451f3530cbbd183287521498de8b1d5a0662efc5c48d4aff82ed9d51d8d6f214198577e8a95f9378bb49028aa424d74bcbbf9370b09f24fd46e9daa4aba6221e943897938bf5c9acb2b2475eb67b88d39c8868d159244878b23b4e4c449b10a492a67d7df6f490bb15021297d7c3abb14f3c8cd4d21314673306d2f3fb53b861492c6b6e32e962c0a49ea2986b3f1cc2017141236a9eb78daa257cd7fc21d35c9ded67cd4dbba88e184e4334fd2abba4aba5b423212e11189a09a1d3d747020461312cc37cbc6790f711b9990f8a9a40875299408bf428010630989ebd7aa96db0d2e42803e88a18424a163904968dcb01ed3e791848453167a39b65e558f8404a1b264af259583cce908495aae4bbb36e5cfa61192d3becd7da877b6d417213905956a614622689a74eb88a587282506620c21396f30b124eee4e82a8590347aad627f3008c99965f3d7f8a526ed0021c167cc3fc7d99357152f74fc20d1bf3533bf443e48eacb0d1da24ad55fb4074932dde4c6645d7af81f283d88c183a4b2bff29316eb3abf8384d323d7522ead3e99749068997450eaf2bea1fb1c246936b72842f9ad577090204a7f122136ad8daa6f90e8d7a6dfa6f267ceac0d92635a2e554d31b3b73548527a964ebf7a72bb940649624288f08a4957e7cf0c9247e869b4b49ddd622383641122f45c4c423b9db5a3475d1189442266478e1ebc45881183e494d48679ddc8c9a08741920c953e9310b9717e7f4192cdaa5ba5f8599b6717247f8eb18ffe7b29a9589d250362b420a9c3829219f31fa7372169761688c182e4a03193a76a7c6dda0bb50f2dae8844de23912eca66b10508006c00631549caf406958325594d5e48dad95d5145520c1b136dd1d62b038c5424c6baa096ffd6f531848ae42cd61a4c9dc8b849c7e37b7cd1cbc32c8b2d4022304e9160e9a468146162bd02b9718341f63eb4b8e283c78d1b088844788420478e10094422918899e2507b27638c9961643dc02845e2afe88e1e43683395d58901062912947bd2125a39073ba50f1b1f5a60f111310331a30518a348f64afa2ec69ed2edab2892e2e952af3d719be2ef0618a148165bbd64eaeea3390b49cbd10324123103b13a33c000457292dd167eb9825d7643d244f03c3e91f4b2a2f2c59cf2d48743d2747017208ee33381e18924ed9564f0b1533a2f0b49c391e3460e04d289fc2e7d4cacce02290117e90c274c9a84ffc7cea1623abee80112827f1d190263138951532839b9b178537f81834f04606822c174cea961e365de4d960bc0c8447248cb0edef29bb26993050c4c24acc6d6da4df227375f22b1e7ffb4969629952d1fc091e3864e0086259252cc245b6fff3b6a2724ad12c916e2bde1d13fd6867716804189c4a029cc59759cdd749a44d29c0c39dd34eac208e14908011892482eb9d9f2bcb44c99b40b2ed07b24922a4fa7eca931c39e1e9296723cc88e1c90f8f23d9aea60a990b4127cc102c62392c67256d9cf3199e388c22e6393a6ff2169660ab073008c4624e7fa8b72954773dc2824ad24e6fd0c0c4624659dbda815df3c9a5cf0e0620466c7f3d8c11220008c45248cecd20f597379d55544b266d0cb9694ccd39e4d44a25765255fba0c640c34b490389b9ea4acc9decd310b895e9f736e6ab30834b090705aa4964cf1275e63481a0fcd038d2b24db6e6727b5ec20220b493bf6a1c5153dfe8ab3c2a1ca4be9fd945b77b3071a55d8949e93e1f36b44e5692fa0418584f17c7d7ee132a5cad9f828097cd8f8a8087cd8f8a82c3e6c7c14043e6c7c14161f363eea011f12c0e283c614126c64760ca62f888c14122b79f89439979b6e2b23684421417fb018751b4a897a43d272470f1d5cd0804272ace66c257e17c4a54f484ab14eb3599af56817485a82861312e7a2e7e5703a98855e12349a907432e6946515db92a9e431582e12349890286a94ecbe0c253534a3b184e4de28a6bbccb2059f43d20a0d25246e093d5db2d5f3b7a311349290f06759630ca3a3c83c4242d25bba0a212f692ef1ef11681c2149f4c4d6d85d52abee1a818611925c64ee2cafa13399b80889a2e49b66a98e69849d55a04184642d753961fad356cf212475df8bbeffb8eed7508186109284fb966ecf3d08c9222dc77e110d1012b7dac36c7f4ce79a3a3f48eeb7f1ee9a51963c46c3078915f4e9ec1662214e1e5e380848177a4da307491a3afe77b6fb8efe19101a3c48928b31731e4bd963ee17a0b18384d3d9da2f97455f2aad1168e8202944edcbc9281089fc8dbe001081460e9233697adc98d945ade6c3868d0f6fc0878d8f48a49081060e12b793bccccf9d9e5396bb038d1b24478f93f55435259440ce7044c00211d0020b36d0b0813d7739e9fe8fada690b47210f8b081810f2db0f828e56c0d68d420f12ea664a926d34c551a2429d369fb84d87e40630609f22be328e5393d78ce073464909439658cafbc52a9550c92a3583aa1162e0a7cd8f8a8097c48008b8fbca1a30b2e763c0e1c5c1c25d08041c2e595cfb135b22a684da0f182e4ab7a75ddfb58614e172459505d4ab7eb767b121368b42051357a0c672a8dd506d16041b2e99a6fcf9a625a6815c95ba1944a327809575b1549272eef661c99f308995424e7e79b699d6c0bb584a4e50466a0224929754a7de35e34972430e314095a721bbf512c37f7a648b07c1bd3d539938afd952229cff6655fd59f976a1e308314497fea77ac72ce1002c9e13b8aa4e06a156436796d5d525124ac7b7fe84e8dd1608e1c3a407278862231650f36e2c3858b3a1bcc004592ce7f174deb668adb84a4290d667c22b9e4dabd06d96226a39034901c7e6430c3134955967539429fa628091ec7efe844520e0b1754eadd92bd85a47d6162308313496a32dedc5b4adbd82612f62ad9a59cf3e81b2487230c666822d14a93fa89d8ac56f290342f3291f8e1df9256d3d13326923c9f0a9b96e3bae71289f33aa67ea753fe7521692039fc5c30c312092a99184fa9520e3a3d9548bc50b3e17e955a5c0e49d30192c3ad05332891a4b6ac5296e9e59b3624ed6f341b0b664c22e16aa388c9399d636ccd304312096ef963e62df19d5f8c44221110d3199148ce675f42861191a23121696f8224c10c48248a5a8836f7d98c5686a49d11cc784452cc5a28f15bd5a9bf90b42a2798e188c4127a47e6ad8b4e1a0b49fb1bcc03c4404280b8e851b6e3439063c717331a91a473925d4d7953379d1149dd2273286171943ad12212b6ae343c28bb905e86a4993d6a80b1198a48cea235a6101d6ce7737ae0e0a28b4424567a865aec68b983638717bd17988188240b4bfee14d7d0ce286a4150f661c22e1cd825e8e3fb5a242e0cc3084193ef6b3e870d6c9425c1ae42b2c736bda94a285198448f678fa84b668f2da4637ba47032291482412b9620108c28c41a06e2fa58a921ae40f49f363382c509629982188e49c6df47d55da0fba0f4973636b811981483e37d37fd9964999199236821b39be40aa0510892194ee24426d2b7a5fece8a15fecd0c28a1466fc21d992e6a4b34187db759230c30fc9de371e97a63e2467d56b562aedfd69c487a4fce9b9fcff524de6ac1166ec21b984c99d13a69485e9f490982fb86dc6aa320fc9d1cb6eb35f140f49f76516933a2ff996df213969c9eb1237ca72ba94812c61861d92ce532c7ca63e211e05cca84382d09a9e848b1eab14ccc00c3a3442a886ea1263ff86a49d197348ca3146b6da9a4add3a0233e49018ae3246bfc3452e1b92a61f981187c49b51a9d2834e42680e480e3f26980187c4f2ca74357af45cf043d278600966bca1dc9060a3b51eaf3742e6e2468f1c38ee4630a30d49a2222e68d2bf502a6576f4d0d181196c48d00d72a5b4e51c3dbf630d89a636b7fea67c3766a82169332813d5f3def47c481a17652098918644d39d5410b539c59423c20c34249c8914117a154ea45198718604a5949f909b66d7926e86e450fdf1a37cb2f338cb90982bebbd97c7e89f62a5cc2043c2967f72f7f92474e72169261261c28c31247807ab7c42b73a72ec504024a223c70e6fc0073e8b48a40b2e22911962481a556e32ab2a7ede38e4438b2bfc06175ed88c3024a56813611ba488b6e8d9058e036680814ff71973fbb4f885a47d2131b45a37fd64cce6a09d20667821d9de34468fbd9829cc0cf8b0f171c5878d8f057cd8f8483e6c7c28e0c3866a1712df55b37d2ee175f1c485e40c63dfa3c22c4fe52d24074f97325632c718a50a66682195533e939eee2c24872719e3422c735bc54272cea083da50a269b7bc42d288673eb597be82d2592141254f3199ce28329f6c46152c6dcbcac12aa714538a29337abb5db80abb9bdd871657981dcca042e275d8b8fe297ad2610a49d30f2dae78cf61a3c757608b0733a6907023cde4854ababa5433a490a0424e35a6be13261714cc884282caf699f1a2af74b341980185c4dc0e6db1b17be38d16c44ece7842b2e612535a1ff27e2f663821d13bac64ba794dd567485a08c2c13b1a0407171ff83efbd0e28a1e3972ece8827f478f0a3c173bb488442299c516202298d18404fd14d53ef8dac50c26247a5e66f7d0bfcc29c7c58c25247710d272c47f97927266b105089aa184a48b8d717ab3b369f9242468bdbcaf9637c9c51a0949d94b7e974cfff1527c84c4bc0a1115ec354282289967eac13795a908091f1efe59de7acf2742c207a5d288fcf3b9740889db5f32db161442921abd993cc53008c9d984b2acefcc2c2a0442b29ad4bad8a23bf45e7e90947d74ce6ce7830411f61b36ae5d4dd983a468af266ee37fd618e341621a9d95ffa41d245d0757b3b00e12f36ac7da17d5d9aa34302307092ebb9d9be637741a0749aa42abbe798ebf79832475a35ab529c472f536488ccd25a754cca43a5a8324cbd96629e5d329e89c0649f23c9857b438911dcd20c1d2a767c5b93291b30c92dacef2c566f37ca118248ada1cfebeb3754e2e0c92d2cdb68592ea8c1724ff5d0e4aea65190fca192e48b0be98e4e34f7b8e39335a9024449f9299cbbcff9a192c48929e4dcfaee550e16356919851835ab098d12d8eaa482c0fb23394be97853115c9a54c73ab2ee7e0a2828ae44e753bcb683945f2eda6ef4ebd1a231653247ad850ba44673be54a2992c4fe85feaae829f72345528a51bc2ca98bf182378a24bddedc175d1489aeae5d9a39e6dcb68522290919458d322b191b1449a133a90b1b663f69ea13892e4a6492a9844c0d9d271233c98a5529c46d0c071720df83cd3ae18e8c5bbbbf6d23f300199c484c6225f32bfe4d24fc893f8ddfd5ca18d544926a6829b9f031648c3391b4de1aef73867e3e0f2612b3f87525e9e125123c8c341d4b755f9880b130698924fd9a2afae6dc257141ea3eb4b822f163614a0f199548dabb78a1740ec2372d831249325cfd7ab4289d278724c88e1e3acafec89884513b6ae524aeaeea0265e0ce17322491249355eba5cc610664442231b9464d33ea9a269442229148a4b4900189c4baad6fbf8b3d22d9b5938bfbbfa698b32312bbf38d1c6fddcc1c6b44f29dc69eb55bcaaa970c46249575adddc808f73c1791d44977c43fba7790110d3214919cdbaa3e45116b639f0f2db0f88844400c2f11096a46d59a9bdc30eb87a4b13f20758848cca2d4c7ced70de1fd19641c2241460979d1b796b4980c91fc22facc76f363aa6cae1009af2983c5c8fc241a2112bcfd457e7aa5eeb866f161e3069194feaf2bc7286b59418620922e43fe76f4fbc2d74024f66c12b929850191d4374249b1ddb4c1ae48e48c20123923b8fd4352b81513d9799f5310fd9058fae64c8a8f506d497d484ae253e8cf4c69367632f890ecd1aceb3d7decda7e0f8643861e923cb7df8285f2542ac6a3040c810f1b5ffc03743004b2d0c15de0301979483669aae7c3b7621e914824823c7070c15f788e04a408f0684024d2052a810c3c2466cd2932edad9bb22c023bbe871711f8d0028b0f2c1490228064907187e4fa77ebdcf39ea4694d1764d82179bff336af4ce73ca3312dc8a843e227b3f27e95d3ef7ee1820c3a24c5e4f7415fcddb624c1664cc213176ff2b898ed5fe17392499b4f95471330670180017fecb5aa954ea86016c21194c56308da9db42e55a70e35b43c6187e1747223abee861d830802c245594ad9cf1792c24970c2663da89e7ebfc0a49514673bf7b66be85ac90709a7f76b2765afea942825bc88fc98390ddd95321e134d8989c174d21b14bb3697a897ebda414924d88aad51f1b39134621b1e3668f8aa9ce190ac9b9726955b8acdefe13124c8cba53db4d7dba1312e54c7c3d868a418fd784c44e7284d2cb1bf59b094952d4fcd5aca84a295b42528c8b8f97f22b21795b523ca82b19932167470f1d2618401212538db2f1ca9befb208098929ff097d1d439bab7b84e4719bb93fb9243c3542b269cd9d64ee8d29a61621795c4e567f5eb6ae9308097e9727674b865bbd0c21d18412b1fde661ac3a21a4d2ceef6d3b6cad20247d30193b664148130261004048562b21635e0f9d9bcd01fc20f1bd3d7dcee3bba8a70703f04172a6e6f75d3839ed2f9930801e24be7a4ada1ae36691a990b4828401f020d94bcda57f9eea5cff0e0e0d257536eb89c83330001d249a3a1d742fc8dbdbe8e02eca007250b08cafa61f2a963ae1201db467f9a47446e9fc0dd813793a98fadab80deadf34631a5ed942a8067b30153fdc69afdc300d0e739d3aba8232f9f10c8a1f453bc9780d23b4039041418bf9f8dd55d93b8018a433851f958581495beeea144f78d2ed0b0c66a56329753a6c5517186ede42866d533a2d48b8a8111fc4cac33a0016f8f1f7d28cd00bb999551c36585c853cfd5115e69a13fafea5c25c2a79a891a1a228a7d2a8fc3cbdb93a056b2a67130f0b993445828a31d7bcaf45c5684bd1767c85eb3fab8c14099b5bcba2b58c9e7fa370e6eb3eb62c840cd71045b2dcdf6ccef1b576ad1ddf830bb3bfd12b861aa148f4cd5dd61fee2c37098ac4acd92d2732e6ef793e91242ef365ce92a1349e48b6107ab3d426b1fd7d27923dcb59a9a034e61c744e24d7754a498c369942bd89c43442e8d724b65392a92692cedbdab4726bff66870a353291207ef76222a763ce87807b5c710ff8b0f1612b851a9840869b9da97cd4d225924525f978ed32b7fd5b02954f5e866bcb551c5522c92e3b3fe78c7ad4a004236e3bca6b335fca933865b95b57cab0efa2243ef1b349c5d8a1ae48a05b5314a1c3a75421519041e9cebeabe5a87a3ab88bb21cd4784452f08a5db1f98bb95c1d0c81bfd15ee450c0b182450d4724e7d49df2595cd228eec80162b7811a8d484e95b3b8a9f775ac54a00623f8fc0df1a431af6e42d2bef81080e860bb321ddc4529418d4530fab2d3c466cc40729cba1a8a48ce6059c572c9ead9bc711ccc800884f4e002039148952cd448445210e6a2b973ceb6dd35109130da32a551669e2975882fbbfa8aec3c327a84234716050c350c91b01ef3945026739a172db0f888448c50a310c9765254d8d0d469e552b0062112337d8c9752a325a11e3972ec1844a2478b16d325d1f84ac261763504919c59abbed762928f7319c142a146201284afd6767cbb54fabe2fd40044e2e7c80eba5f22734b5da8f1871a7e485041940aa77b653af49034335d408d3e24e83d5777fd9e0f49d22d3729d5b3d032f790d89db4f5938ea2126ae821b152bde6d17ba533e47948aaa4a62e7b41a8cfa80ea1061e12af46b35b903ad2e3e50e4917be2e34bed4886d764810b5b6fed5ef0e6ad421418356984b5d73214e74488e39c6bab4179499eb7348b40f179ede6c3f942a87a44d42838e5015ae1d8c43929ae94c5f55beaa3138a8018744379d6ff684eea04c266b50e30d0916cab4685c891ddf0dc99934c510bb516c64ad831a6d48ee1f351f3bb3fda5320a35d8909c74defdac9b2bbba5ac2131e5e866e8dc1999a61a9253e8dda421e1b3eb4a776dfe90d190a0ddbd95c1c488967e8604f9a1a2698b69ffe1364392ccad2ed3ea79c173ca9094fbaa3776eecaaa890c49619a6468a98fd39a1d4392d26159f53b848e2bc5909c254ce797ab4fe66718925774d0b1650386c41759ba2a9d1a13b3f9429236f1fa58ea72b4d2bc906073a5c305994b23b52e2408a1c39fac8f594468b890b827bb41a9cd1612fed2849a90d79e64d442828e77ea84fa11a15dc942b298284b11f116fe332c24655d9296332c4da5dc151264e54c3a088fc9574d2b24a5911e9f528b67279d2a2448739359d5727869900a095a66e6559be55e54a690b86fe15ca46e76084f0ac931549ba95259a14d1a85a475abd8b55da190f06e632a9bc7176db127247b5fc69648cdc154e38404f5e172b9fe0611a969c29ff1b64125128944eeece8a1a3a4a0061312845d99071935335bbb418d25248e90d164f8f12c4a4a484c6e4a4bbc4871bf38098917ef496b89aca6b320212993c71bfda6cbe4a747481acb7556ea9f726c23249ebde510a73bfbcf5b84649169f496a8933a1f2442721293a2e4f209cd5d1942e2ce8a68f470d9dd8d109254b5c9504275caa5ae2024c954ded6a3ee7e39034252de303556d23e8bcafd2029e6c609cfb9a563c93e48dc14b4a974f9dcf6f33d4850eee25ea65b6749850749e365e2ab37b383e4b31b8d166e1656733a48badc64d237c8ec24333948107e5ba542c53848dacdbef9983b374812b3b931e3558c5d521b24dfe5b9dc1c27d4a841b29be9756bbb29b59a060926f3930e9d9387fe76060963f964ae538baeeb9141b2988cf58d956947b431483061214b6c954a1e736ac020b13df6bb05539df5f11a2f48ca14d7d53c8852a73c355c905c42f594dcce76d1548d16249985a8d70b335722afc18204ef4cd3d06c3122e755248a7ccb4e9e1b4766a68a84cd74397f3afda8234c45929fce4cdd2963e6cb50911cff39db16a4b7c7d829923707f3504945a648debcfbbe501fd15f9622c984d86713234c05d14891b45935977149890f975124c64a51398f56b936a12892aac3e5cc6aa24cfee1e9c5c7eff082478e1be8a3d38b0f10bd5024ccad9db4d3cd3ebedd08ce093440911c224c788c53b31893280391c88d2012e922c78e057c81d03e913c1ac7dd93fdfcadec89c49c9a6396f84daa9972837144e0c34627923bc36bdc4e27f75424020d4e24fcabe998dd6c2229aa8fa6ebf7303e1f4d240759a9396a07e155aa0422112fd0c844e269dc4db5f1ab714b4c24696fc5b2fce125923283c668237ae4a6c512c99e9ed47aced34cb18240a312c99d5247a96ce788d8299194fc472695119a44c25d8ae99d4efffb4c92481c3791e67174ca958b9148501d841895dccf4aa501894c7d12aa4306dd69e52392e6cb55b3887c54113b22c136a71026ecff63ca368259f712e2c27821698506231236ffaa635c994eef952d2251b4ab82fd07d56a15452478f8a6ea6e109d3f9688e4709dc2e7d1d37262cd80062292fe672f99305d3266cc2192e2a8fb18757563474324c93f69afa7c16455104f2192fa53e46b6d7912dadf811f5a5cd1c30b1d17031a8448fa1bf9b392a53b5a1a445252b37ac94678b2d00b224925a59fe71d26c2cc4024e694c52fbccc7ac78a16587c44223d10ebe84206340051ce15ccdbac53271d59593dbfd79d1493a1f1874439cbc17cde2e080d85a46dd2e386173bca0f4939b6b69312fea24dbb39a0d187c45c5fa29fbd69ea530e0e68f0213986ce49eba51c99c68336a0b187a48c75bae59d620c4a9bf86f6c0a68e821495baac8a6161325d379481053b2b6fc653c2467fb9cdb43b977485032c7a4e99256161db443f2da58aa8889ea195d489a832c0d68d421d9ad3f9afe8709af28d221d95236a1694768cf1c92429aa65042be482412c9620b9012d0904352a611be66c2c13774b433c073d8884392858d7453aa3579ce095816d080c3779e52c5c60d7aa73222a0f186c4945ae3b59ed208d95d41c30d49b9849c7db598b73246a30d891973acd09484ee546d485a2954a0c186e4334b6acdc533783685a49999800b85028d35247dfc8e231777556e4c017c81861a927295fca714a39c3a73241c5c5c0281461a1244ad72ea8dbf8814e1c8a1aa041a68484c4294c937cbb80c3b248d07173c70e4283333335b3501ec80c61912bdbc746b18533a0a628a1912654327d53bddfda613920602f25e9432051a6548aace7e298c6fb6745f481a081992323e654c73356eae86a4ed13688c21793343c8de7443d2ee9040430cc95671b5b2c80fa22c672d0e34c2901c459978efedca5d1d0e34c090e8ada393afe9bcafbf90687a336ab820653fa5bd901864e62851955a1b68742131445ea47f0c4f59e242e2c7d12f9a5b3de818b485c4984264afff4c278c3da8242ee643d2501c0a85028130100c0a9aeb9604f3130000001016920763e17848d495791400045642324a2a261c1e1e141a8a04627120180a03c2a140180c0683c2604020180e86d2d21cd47e8711bfa02112f9f428e6cebbdba83bead515e25eec74668d33d28860f9b1e8258070f9198d5aae8f2f40e0e8acdd75b9f811a710c019e2985bbedeebd58043b035b8295183acc0874315f4e1ecc50fc7538003528fd297fc8c0e741bdc3480bae1487a863106ff9556bd3018e643c27dbf012a03870c1c5b7076820304870f7c80b011133216577a137ea717d0ed00821f8f1c35f53b53d2ba2f8fedde3ea3f317d7abadf461e8247511b6018c0ea620dc185c3038bfc126f4061669f53851576c75c498fbc78952622a982a1f4db891b8323b6d14db17dd0f4f7d130723351e453f50a31b0b608261507211fbe03e8f4faf99c5887d8b465a3fcb4d6fe00ae5393883fe26d21ea0cd7670962db41f5e2ea5d40a3533b1e3e62e42d6e9d2a51c9fbca65f0185baf2e87b178f047483d792dc9add7dce28025811fb52abb1a707cebbfb4884ce139d27608e6e56e245b4e5e6471bab6a486f4152da2f112132ed288ae8ca487863fbd6776a4ad2a9243edaaf0ce66fefb5b864e30078a93aee3146c84a00f0d284a93bd61d89b432f2da0989de26d90bcdf68881aa105f0ea306c9de565c4491cbeb9245844622a501c72ceec0616b38067e210f93319797137189dead2dfe882f61c958f37faaf5080d10a361c066a56e259d5b55273e3ab849102aee39c4f79d45ce9e54e7cd27943632472c303fb45406a70d26147a67b079b4c67892f91afd50fc0e8a7fade3a424818c07f086fe560fd2128c010b5c0e796a4248dc49fab0fdb7c65a306512b140926b99d2b4d421c920323996b029d40987c5c99724b149759c53b97af4278c788e27447592e8681302fc60ffcb042c2144a9edb21ed5a17cb2cc4ff9d522472c79c8aef196abcb22c251b91e34659a5d40b15e660ba2b8e07980ee51560ca5ef81b98e814757ce38c66b50a0e289205b8f59e8f31bd15eaf06200b0f78c11ee3080fb6f74c8922f25018f9fbc065919e4b658a8e25e9a6ed044c0be6b8390e1005d29d41283d9820ba9623669ff9c63a5f1b32b2aa4d39ec24160ad3ea9096204f9ab7e0307d6958277bb3203c736cf18eb9490046afcd8191a1918291c22a0aa3fd9786b52ded6304fb547b4410ab7d0c8b074aa84791d007f15b7cb966fb35057e8a570cc02340a77b913725631b359e5e4270b72a2c5f6ebbc9daf2b32047cd61e915fa4ddb12782e3f8edac192a8196b5574d8351b95cb1856ebc6c48bc0996c86ce680cdf33f436f0c69fd1e0071a25b953d4b7b4632094785bdd5627e496a5ea47561c418ec7e0b136c6921d7e1aaed30547dee2e764369801df9de57dc7abf88376cb15fe53b4636e5fea08d300a58dd54cc514ba4e123e1004dcd3dca5259fe6fa4e630b64b26d923581044497136a4f7b153054f7e5c8107bd2b1c9133af84505b4554cefa5e2935f2503325d41a8bc03eb2821c19279435ec789d797dea125b521cd6ed9ef1ce7a7a346f9da0b162dafe39bbc366b7b8d55a46db3573288e47f6df3ba4e5e31333498492f025ea9bc4244af37d4be7d7ba35e6f5ee8bd9a7b35f06ae4d5ecd5d0ab815713af30fcf5442b37d724f6dae885969745bb5d0437b6e0c4bf6469947fa0a5c7f3d07fe446486a7e24934c5f37060662f9ffccab3f3aa27ba4043bc96da8bb8c4df25fd6abb017895fc5c0d27b25f16ae995e6d5d4abb15703af665e0d5e79bcaf279ae3ae25b9d7b617dae375241de972a1abd72202b06bee5ea6f5d54724a743ce33c27c30e2784df7bae775e5b5bbeb7ed7a6ae4d5d1bbb36bc36786df7dae6f54d5fd723813e1b929769af035eebbc4ec8eb384ba3f2181836e0f94ee5126df9c36591af40578dc46f4418c2805c9a9ccad20ed388da89dfb1c2d80fa22bf394ddb463a9ee6b85ae1aca6a419a1bef25250515dad078747f013d52888b20897a4151744bdba41f5341e0a875f690657e2410f9179426b8cd4b4366f72dfad28f3262617819e583987436c0933672a9dc64f3b10b54efada4c08a48c6a6887521c78074e5ccfe1895d15ff3d239782d7f85d009a57c8f1025230896dbbe2aab14634e6d5b03cd7d835053f73ecd2e04562170847c080c017970330f3a431f141751f7187764b5b2dd0a3e0c18424b2f17d9d9034d531413953c2f8f2965908c8d3b8c5f38eff21fad1b2bca6a8f31874567d77b901f3adedf2739a7f9e48745adc7d34e7c3cee08f1dffbd65d5577d37dc3011cb62fd0d80b87e70b4cc7b3c012ab7680b4d96eb08e3a359ee99b0216d8286be809449e55b66f0042824961a70a331c757df6b82d6852141af579f997e7e929a6a2d96bca5cd5bdaa4db57127d87d39c596cbe223352e7f67af42fb8025fd03f5bc3528e9ca0f5388f5fa83426e469ef0d5616a7db5d8a123c9b465ce44644473a1fc2712ee990be40cdaf4f35b46a5d9872125d601caa1af885f73ff8c654ffed36fae06142fc08eaa687afd88344248409d872310061a45c1a6be35213ed5e9d30ce677d42e66f24f2f62ec4b0cf71176d6c75473b81b045b1d2fe87adedc474fc31d963487bac2b91515c2eec39e1abe697c7596a1b86a8d8a30d26725671018b0d74582b11c1194828329b58d98186815693112a5aa26cc4481991aa2685933f3912e2bd11858eeb30aa1cdbfb5eccc10163d37e94295e04d97c9e68e2a295838946f8da3dc0524343890e17b734cec52b70c6b9b6f31f492c65c1d3989831bf42e2f81745bb314ce56fc557a0c75484d3a379ddc303e8656e4340909f4561c7dd5185a14dcb82daddfc8f56975b80e2d1fbaa1545e7e14d1ea59ef98efca731d15662ecbeebf401e03b06f0b7151a99f943eedb7c41a1273412797451317e32da398961b1d313e40264ead1df5c8f2e024a9a936526252a40e98906a8d41aa587dd4741e251751277a03b39849e18c9431c2e4205b86593a3bfbb122c8559de532a23811d30bb1ebf30c321fab5e1e6c3eedb4d0a92e5410691193e12db0c0b62f558c780f1e731e703094fc9fb7cef03a3ada5bbeed29c9f6440d0b15c31363063b1e3234a3daf839802fda6589901ad58a9d218980462c9fe95e3fe054ea9913eb2dc29032a693a094fa60a721ba03f1e2739806144e9048e6f08d90f8450d97479ddab9fb22df6e0514d77742621853994328634c35c4fda6843bfcba4fe751680ea214b38582e49254657c194f58a89c7e742d82f4ad643398773d0ff275a7c9edc9278bc2272a5392d59c74882549895bdf5936eb98eaa511596d12065fa876c6b122f923a6e29cbb1257ccce7ee51344225d6a8ac5d6d60cabc3a3dc891fe440c3ef739a59d48f2222d75464deec75a7b9915d6163a01b8f79027d4a538a953a4b103116f1744e7007431ccc6a013d237f04222de23952799f40fdc12e3e4a8021f032510114359e346f835e157f028fdf91eeca388de4558c1799ad8e682fb67bdbda08cca953a224bbbd9c8d5db7380a006855dd291b7561dc3819d5898e467d50f3955dc6d9f482363d48cab463d0a5a1a0fc33159a9e92d71b59fc784db73eec55270b79e80719384650b4e46fcbfd5cb0e5e763d107ef5dea5175d4d82a79430dd407a811314118962ff8c9d2ba88f1a140b5d359bb828a1411b4bdfeafd67828fdb37dc5e338cdb37513684e45629f5c975bef42b2ce62a26f572018b0205cbb8019fd7aa7773cf28369fe9f3fbe2d5e71eaeeee13e07ae1ecc72ad98f4b87c20af19cc5e1a3b07411eb6a88fc9e3103fde5091f2b1a4d7d8c9cbba5481a4cc6cc13626f57fdbb569d0b3b6ca620a5ed663dd43b081fb6cad749bf12df880a03ba0e78f4b374e20370f5dbd4f900d62f650624fa0f22fbb51ee32d3d68c2503b7d1b9d66cbfd5e7f7309b4addd4ecd9beb6b7bc0299e3ff3d6d5d269744c7e150c0f9d4f859a405e304d45aab362a191e737fd7f45f8d763555270ef466681999df24b910a503ba41f47f5c68005460f338cc11c31437ea1ffa204825c293c0376e2690711619e62acfe9a9aaea3cf45738f76e5f49546f2f12555fb649c99b24a148c969e9f22d1dd8c3e695adc087de1fd1bfbf67fbab5728a49e94414fb9a8d2c685cb1ef0ba2092fd7c7d422d88b4ca6d4057094a4d05670623843f769b2234168ff9eef4a50fa907932c0e4c0d27761ee398094251b77c18d2ea3c53dbf8e18711d1ca034c23282ebfd199c7c2e2b6e891db9d20c15d562b606093e8a4833589b37ce5fd6a69788682de3b6527a113ae44cab750fe9ad3748df6c277bc0042b8013096b877f17d30748a99b205e40b2b889ebe81bb155aa11805c550f2d26e24ad16b31e587104ac5a83b9c1d61420e3aeddb34bd520280fd00a1a013496f14d0dc5888eab677ba57cab7a853966bd835edd965b9220aa23ff0452b6435a2f397f98d313ef4de5dfb1925826d51c44f318e5fcab4fd04073ec991cae975d9ca021fa5c72b9977ead24f5065e46a0755124d401f859cfd1183920840319504076f252f098350ea5d77dd23f0f9fb895910fc8fc0d769c98120d520f618c048951e913d3eef388614643ffa247fcc2622f2c862755ffd22a01641aa0cda634fa755a6e142646fcef14d73d2b5e5db1b99660cf1040200ffe942f478235fc6908d6ea56300e136ac3989f9cf63c1114a383252097ffaaf5aadc6e5666db56c4cddd4a788f31f472c8f02957f64d638eaf2ac1e7a9342822738c22fb2ab14ba9cf82328b1fc12b8c1c6df798febbeff7dab6b2c6b9b04ce3698e458f7681bf0320dd77c76245ab1b6383d6993ca07701c515ca23601052961711f76d69a9c646b4260383750e4a485fe3f1082d6e1ca74e4284b36fa405e4248c289b37cdcf11b2aef5abc1143161687adc9582855e12341c0ad376fa55b4c6e36c2d61d8d71673779b411f37dd246a3cd7d430052ad5ec10297d330d90667df14ce7c3198b38fbc6251165b93b3406305e24637cab9b4572934e4023a06a22d07c661a2b53485375dc07485560ec062da0e7c35686ece1e0677d3d99094adc45cdb4e9e40a8b4cf4d235ec795cd811c7195c00e702edaf3a33153d8392a711b89900dddcf4eceeffa1fbe1b9141bab124107905904f1608730acc47a738297c165451aeef620a7162f478f8202fbad29ff814c5ce6fa674d739c96c2f109686194938d03b8129f7d5bba5a2524fae0b875695f3811405aaaa4c51245a5bbb193d6ed98e312784f606beb7cce6a4412ed862c19450a9ea4e4464b2cba95a3095ceaf1bd6ad7be3be5c641ce0068388cf20c7729102e148b73334cc3faf4e6cc40857360df46326f580c761bf694fb75396119fd5ddcf03dba0bf3df6807152ab7b8c9da92a6ed921137f59fca4fb2bcf8528a7d507db10b7329ebdb2ca2ba547a56460af4478d6151d6aa90e794fcc3a8280f03963e10799daf9f6e8320026be84e50f9eef877f7432a5b308164452b85da159241255983209601d36b8dc7b686b1f4a5432a9430d74d881a2f50b13cab720a471a98e4c6a9daeefbbf48c0e06446a2e2014aee9ea4a934daf17537295bf90095e33774968cb2e62106be15420323ef671d355f5447213e2c0e1b30e202f5be206befda42558dd6fc7ca8faaa6fdfb4416a5efcf58e5551e82ccb507d8cb2dd8174f14d0fe6b62add66b4cbd7863bb9ebbeb82e839028147c381ba0217c1be6a09ae0beb38ebf730b730299590c0d232a21abf315e5d069340a1e0bbeeefb1eee09d8454f6fa69319cd16562b33b23449b63449d8f55a853f27d8e851a8738e1aa8490dac607c60d33f6993401a0a93eeca3af6b21e9638c241769e919b554a99a2e29f75a94e7a8cb7bfee8663a477fa9d21a0df0ce3662ec1b8967353c685315ea5f599258f3c648ceb8301528e2c0528196899ca2a4a3a8d9b6b3867ee437f7c1ed11ede35a1197bfa906a86a0ab89c555428664f8710d3bda35adaea98f70c36be3f297ad09a5ad31986bd89ba6c5baaf8b5f4f34d017defa327c030bd5f6dc9423d0907ce049b9be9a5b90192d1d8c40026dc17795fa2b2389a530a49b9cfb66e44e173b7e4924a9d3fb3e869b354cb9248042abba8ddc4af9b7b2f7d65bfaf9ec645c4e33efc38edd1428afd5d8088e93bc72de8d205de454f5d8e3de77113ea7af725068e308bebf82136b193f1e46720407a2bf7f31e072ca52a72eb18a540115f1db0a0b024731f8d05d23c3b5898b24d5cd421a598828472d4f3b7cd61d20d0848d49cc2e215dc13214629e3f3a50a4c4a0241c1bfaacd3c0cea11d2d84d98c6ed16c9b2e9094050fbe1b854d9a461840f782ca505043f817d5c2250e703198212bff0d081cc4e81070f30e7765cd5ea04588ed02d2baaba9b4d335352ffb8f73e6a148216e4584ad03180bf96cde48168a34c9397445dd9566bad4d5e6ee5fbd5769ba420caddff14427fdf71dce8a46edbace525b6c91b6dc4db8ce9280d93747883acb365062f51e6ed87e0a9313d0dadc6bda4be55c753c700136dacf8d04b2a4551d968f3bdd4eca1c76a68a39d74fa06684bc6e98e48508b4f395f1f878c1e0583d8f2f1ce721bdf12ccf96466b0806989acca2a56080f01f63690267f07f56235a3d62a3303e6fd54d26fa6999f25c12a476130b8b3e1b29813e0d4d24f5c399718854c1aaceef4c307d0651a05a6c272d2ac9ad14b4b185f650df4ef114c0ce8604f910f7e527d9cb7e3f1845293e4342295f410c1e35e049b09690865d76bb40f6591a901c813037a55b45742f8d9c9a5f90666b909be043c0c91575460f8853e68b47e63f51cd9081ecda79c25d0b84d0ed1a9175431693626fe8d06fa7572b0ecc9edd47222b27e157e6309d1c7897928f69833cfe8b7c4821db5766cad5c188ceeb80e532f6d1939b450ceec08ec041280b861346966ff13ea568847e688fc455e2454a0f6e02eb23a6b95ac04636f80bc86e9de1996da700fd3808c59eb02236bbc34dc6e51a3727b45060f79530d3b76afd60c6bbc2526a8d8d2aa836c8225b407e40f95868b56a9fa13d38a1b341f22e58abb5511528059e9761c5c6ab9fe63cd3055bf981cd25bd6940e125a6efe9abe34a552d7e58f8f406b948f963b1cb612b8c21e9d8097063fd53ea36ac9777360b347050c24d75cdef5ae69c995737e4e0542f546d399c838f8cc287d3ee164a67cb717821696d2308a0ce62c320dc8828a0397f7d3754998c8410ea37b9a0a6f82952c029b767736307262dff20cc6909f09259451f355227bf96924518d3cdb4c89147363dfc7abc74c28f6a50c15090f26b27d4a6ee60b759910a8cc82c272aa3c959ccd1c95e9a543912e8aa4c4dd469139402ef31c583a3179baaeae44978dfbcfeca37ae3568260d6242ded79f00bf50e3fcb11381a7ea61fcd34cfc7148c31f1902d23bee6c8092a61c92415cde24d1a330f1c13b491500271a6aa8b79ec6b4e443f20203c4d175affecc1bf29c92ccc06aed21557a37bdf785c25d7fc03f6eee454987561f02d71bb98a7981f868136c73bca60b6376e8789b9c1065e481e3652b51805fd8b4dd6e2ecc99d50d508f9af32d4a55531f2706c8becf3567126fdb83db7fc28ee9cbdb790f0d87929ea689f1c7a32c622ba78aefeb446e005971640d248d4ea418c27c14c3ff78db05f5053e85658d2bd24c9e3ab5a1816c508244da3701bcc139fd4fb8be13b50964a4666cf5a22b98e05c2bcab00f7c68e9debdd638db466bf7a9349472a2e91ba2c047a3e84540968bce209afa11c79a69814bf47b798ec8c7d64a95909ebb2bc69e1763a3ba7effe74fdd9a572bdcd127b41eaa2a9bc4a7127c767ce699d30a62324d370a9c15a2d6e4e4c7c0533f020adb3aed659c77dd28c528fa51f810ea0af89ddbafd7c0d4a96d00f18a306e2e92161ccb9cc5de6707355f3b9796e8af53e8d85f9be891de7827c024424680cf234220d52604eab982396854e9eea57100ab470268ac803cdc96f9a74c293a85ce01fd7bad9959d4f2fad381d309b537dbaa062ee7a9aa2f49951cac2ee20d35927fbb190945ed484a8d4f361270f3233a60ea2c993ec150010d76da1c180af03dbc4ad7bf6de02c6008ad1181a634b647cf78dcae98c1a8a5933c9c983531cfab4f85965e57a7d89bb278b4db1c2edec071c595c6098d8515248ce34337f9be637cc5ce075ebf1828060b49821deef52c33500dfe88948f16b1525219093f42bad306f4c3d2659b47dffee94e9501dfd0a1d821f2b7f98870d98633b1039ffabbacbca0fe657c51b03e58806031c2c558898f392f92597fd611bbb931f3f563c0ba808d4770baf1c8a23cb23265a3c4bb25b46a703a78e8c8ec4e6ac87d0d7799924b7fb1bf95f69e4aea5425e4365ce0dc647bdfbc53161165e34edd28fd7f4e561381c5a7ac0c4963d9895c1dccab5467c5cc34c8b0ce5ef1f44bc869fbc83bc4207540548261ce628a11752850e9c81abbfb2c862ed90aa02c5be292dbd53d7c4941db7d419ae87006ddadbd809669531e2b7a236bd53836d3d6cd3652e97389651772e50dfbf7e02f14843c29c40be501aa6cb80abd3a5f68c24f86b01054a4ed83c03388ce66f13992016ad6e4110d2105e8285df323652da9132c549b4c395e686c4d4e7a187ee2bc48511b57b9736df805fce2e846fef32eb41145a650fa0641ff91eb6f6257d7b5fb670a419ad23026aa1e0913ed655d41069000902aeaf96eb1009b30ec595b58f3e0203ad0e3c4636a290743bc05697d185e4510f27f1cd840fca61bbb1d7c5d8e35e1ff1a2763eacef9af3684c1dc0f651ab0ca675a5ca730cd5b4ba8bcfdebe60e9fd5e3b2d3483e1748ae4619a327a99ee470ddf619f72ae0c78a63b2cdf70e2e85300940a22df9c68cd86b94c1c373cd96a09ace0529fb664db6a18429666c00cff7d93397a81be3a7f6b174de297291554296f54647b0a3f5eb3f73b2350f58f23560c3c5c1594f417a53b997d2c2147b05dd967424dbba657544ddea3cdc6a028d6becef57d557c4363b01566947f397669d52bebdba860dfa090c405d74793126fb70706eb483bf1a11aa34f9a7048da48ad4096098885b9ee175d6f7780322ac91cf0c811b3da6e05061499f6edf0c89cdb69dc0439c3ac46df6b1ba0193c8ed1d837891875b31f9743712bbba5ded65acef52f23ee86a4724046f18c0218053363eed13aa00064ac0ca362a07aecfa2587999e9a8dad738f56539c92ca14aa2b9bdbc97d2c8c1913052eaf2253655a51700c0e06342668e7786263c6387a4da85e0ffc96baddb4687e09eaa10baaece191a0ac95346b7bfbf52f1527eb8184c6c963534db115ee538ce2668501353b507a3a8368109e6171b4c2a0ff1597ef281bd623d9ac4e8cbbdd601d3ac6361e528932461d7143988fc2183ac30b00e9552594e0235879037312ff7658712c71eae96e8f63194e18040520285b5985dd13f0cfdf652123febe22745677b9c58a5e200704c7939bde60e37cb538f2a846c8e64dff2062cb8869ff244abd04218706b8215204cfa7d0d48647429417d5e76cd3ddb50022a4f2e85bb6f161e9c7096b2b43ddb98934013dc842c0d5c1d4cb94e8516af8d0457b2d04d8f525697227ea07ae9d8d8b75b1b9bb06fd06020d441808313069606460d6c0b081280c362f33287f2967dbc1e2778125071b3ad591f4f486650335de73e967b10d9b89cdd65165e2368edeaccf274e06412abe282965b91629ed95aa51535eb7a28b85f060433f7e86cc5fc1bd9ca504cb4bf78fb9cf1cd3da9d80fd032c05dc8d008ce4000ed13720ef54ed2f3eedbd70dc87c10001842dc1ca8424e00093296d182941ddd23a91cd1000f71280694122cffb36495085885c44ad446588fc44d44a6486c80e91160810296993a89b584f168de2d237a701910a3263a1a6a41b8c8280850b1a970798ad280337210760f42c033b42bee1f4dbda81af8e8e64322745eb6903376cee30fd3031d881883273f5303581d43c6b7f7a28e0c64e4f92b31373f985f8b26102c648bde3fe7c3c1d9ad45b556676026e69f18f8fb9002274eb1516b18b27b6e442feaa694b4f3ff52775d81c18713a59751a3728d3cf3aec15c80840e993b74f25521427b683f08cf1a2a8e30cedb38fa4edf14728c480baee8cad3b57bfc120999f0c4578a35e7e38a8f98466f98625d04958d6d3b68b07bd652ea75a84bfda7d0d31021ac196fe0af988c809d4e64d5b500a5a87282501965c9827b57b8005eb28b7c96a92783ea3d3971f581b37322d69910a8a582a8d50cad0b922ac4932e07ea82a16352bc7e4458168fdd17c22152dc5b07cf423c7b729bdb5bce47faeed0e5ec70833c7be19de42457db7a1358c8693be362a6d8e87016a4d828b4d455ec2cddccde2dc8739e892d3d5bdb16a420c6ac83b8274ab4f6499b31b4f89f18f891c3aed015c2786515af29a8d86fa5623686ebdd159ca92cf5c86f6734af45439a5857482b1227164c18f64f532b1e70065ec00f4cfb82d7469ff93b1862004a313e61285471347e1ec2b9cda3def4208cf905ac66b58fe9ac8c479f6965eb772ecbcda2f9e4089183fd74e11a04206e883b21017f972db7d82247a66b6d4ba5ba545f152d44fbcf98e95b999aad592438aa91213c617608aa20a157027c640316a5ebe7b4d3e5688a499fe3932232acd421f6a326d302944ff9ecdb924abe3ce910e5534e0817cb2ba78e5eacaace45e2e2a354db2c2afb9b39db80c4e64b7c3f512ec547a43bf602fa4aba25879b67d07a87e2a3d4d4f3b1ff75554c339ac8ad32ea5f75f0614f9008e99359315519631cf075435de564651fd2e9c31763280da110129257491cca783bb7e8515678099345d80d8d569985b60e31a790218909f180a38ca017042bf1912165b1f4c4f67847284c3077e936b50f51243eaf241ebb12bdfb93618137930be353e125295940c601948e3385d9ba116e7d4a62a8d8cc4622ca32308eb0b84cb58b95252915a8556bdc2dfbba81cde3c703ee77b4117049cba053bd66e63f3888437eefe471b12c304e6ad8c1bdc4d5f8b8d39ad998394123fbef4da3e36b42df736dcc6b69ae63f0bd5f2ba511bc96a6f8e5b58438a64bcddcd096be2db838dbb2164c229a885425134aa40379440dbe81f35834bac9409fa8af5a16058da6cb04db5128667563c65342e1891492e31ff1889dfe8880c5801f363312c85610e284077ed54cfb2c435aa4ae86ada7df5a5569ea274cb1dbd9727e3dfdf4e2a8a7e4da5b719790152611819530ab59342ac6855858e2851da50bf52f26ce5b5e69aea74bd33b3588325b3598af4e39ae4a5b3dd89b30c769ee9ebe904f6ec143bd8d203216772baf812cc5bad767ea23d45d7538ababa4b27d444aaa80bc706aa666674a875f8cfe476b33a43dcf11a20673ece18dd9ea8dc1031faa0ef7fc21c9209cd8793c88d43c042b01c64c8fd5e511aa5c3f1ef185522ce660240c8322f5d9a9444cf95c4ce329735a643f3c7710d80bff1f4af7fdfab47eac3d6e1d1dee0a142e90aac1613913c12487c6c531133bd881677349f6cc122b7284368b996b9aa728e966b99c3aadcb7314b1115612f8955b58bbf4768e90480059a2772323c1f4488bd1d048f3666208a28501945cd23976de567e1cea134abeb1aa92b46be0db20fa783530a1714a44fac8d5f2815d7c9504967ad9f4eb9bd78563cd1d212fc02d34136068d06503a213ba40d49b0366441fa2cd493057b5bb0470bf660c1f5b20b837c9f5c1199056e7ae11b31fc4db83022b747091a0252760e014057fd001c13d21a6ada3f71c5e3547af5008cdc1bafa8f5a6bbb0ca648b4cf1b1b8959e0675e2c5637ace9d454fcf0c92097221276449fac838992227724816a4838c2b539f8373fa599a5e324026c8899cc922e9202364925ce4942c930e65ac33e39c9db3cfc2d3ed8c7d269eb37366525f7926a30a5b66649193ddbdef473b2d8f7d0bef35bb37fec093eb073c85b59bb1de23704026c5b44fb4a935bd537a8a4ccab49bec85ee12aae2f14c3b8dedcc50675ef8eb06bdf0a68a640e2a4f24b6d4209af44327e838add12bbaa5df74868ed0da7aed7e6f675d5c3efc86e44cd0291305a29094420d149dea512f2aa5c2ac61529aece6024ebb44460c3650b4960efcffffffffffffff8fd2887c6ff27f2b999294d58fcab9ce94924c29a5c87abb0580000000000000d04d4484b42d80080001360c380c950c32be6008595bbed76e5cb23bbc604e3975b2d27e51818c2e183cd488385d97dd846c0cfc21830bc690d5415e4eab2c22af438b87990f195b308fd7285111249d3a6f0632b440b260f89cd7ed3d96b8fe32b0608a20d4e79a1094a896878c2b982cae44f2d8a7ab94ead01e3f72b8775ac1e82b562ea7f3a7f7fca71c2690510593ef7e8a4b530c32a860ba0ed3edd8bdea49fd2106be0978f48541c614ccb93f07b17de1397e1e8c1dfd053230c8908271cc5e45ef9e8c387a13f0d8b165901105539989a4bbfa74aa5aa0b02290f1045390175fe36d363b270a64384146134c5931a922a5a4f5ff924306130c7b5a3dabd7e5d07d410bc858820c2520c962928c65d58ae21626e7eac8f7152cdd29305ff48e1f6180816424c1a45dfcd7a1c5a3023870f0e88104e375c4cf6939c7945a8c307af87030100bca0b328e60102968c4ce53659915e204194630e4a477b671f2928629a308265192f26d574e87160c6410c1a493f289193ad2a1555e206308063bfb522ab25ae94a76ac005b204308b6984e6b59e2a2436b05cfe3eb9220230886f19222724ed64ad708328060ce733a692dc98e7621828c1f986329a5e517238aee8b0e2d1f984e9d854d51917f97d2a1c582771e3b7a603e4b27224fd27aa1ffc1a34d904390c103636524b57f7d0786f1bc9274d0ae25c602820c1d18cbf4689113ea3284ff62e4c0604a4cc850fa617a2f311fc8c081215b12722ed3e31ec8b88149743e7e07ad7568d9c09421f53fad5c1621bf1a982688f4707d8f9997f040060d5e1391b28dacdcf2608b5998f3c4a986b4a85a57ebd09285c15eb5b7526e9b66c8a3e8608b5898622fe5c9493c5a8a970e5818a496e7f190b34e8e1d71b0c52bcca9cb4336579f54278be10af349af9b70a163b32e1d5aa8bf08a315e6170f22e56bb7b2a7fc8f1d3a5690e3c79b8015e652a2848cbd0a5ad1bc1b6cb10ab3843b397d395bca35abc2f49f42e860fb2162e55361ea97d5920927edc46936d8021526b12c1122c9ddd6f33ab4c816a7307cca99543d97bdf42800821c15d8c214e6f9b99bb0676d396ca53059cfacfcbc84fd3e91c238497930d5151a8541eb25191f2c6ce6e4a23067d97e086abbd65c120aa365e59ebd3c16d6c4a030c77d6b6fa79f982d7ec258174d84890ae2ba2e9e30db998f70cb35298bd60953dbe92a31312d54789c309e6e7a8a272e5c3db50953f786d7898e2da63b4d984d727dc82e52ae2d970993654c1a1326d74aa329a6730953522b3167ba92e59825cca13b9fe167a9ffe54a18fc92d249e1514c871825cc62db9d4a2cce8a8b26613075b9b5151d7f594918f5e285a4a265899d239130ff8e8be452da729a14240c9e5c256ea5f434e9cf91b6c5230c27afa146c8890e25f5c3470f31f0b6708479dd46ff8292fa398503c7168d3079578e9714bef46584b153b810364a7aa560151c3836b0630337bc78001eb6588451f742122161357b2b3ab47280d1c347ca91e3c70e30767c9156ea070bfe40b085224ca63eaa8d2a199de3096c9108f3a7ce218e85b5dc35025b20c278fbb12e4eba849c637708938ab519b3f5d1a1f562545d952d0c6108ea42e70579218c372a7b50f1720e3b13c2a07a92693b1321727610467d2d1522ab4910262dc95cd28de7b69502611011abcd523e01c2e4e9efb3e45c349dff1fcc714d474e426ab609bd1f4c232aee7605f5c1a4cdce93aaa550d17e3e18430825673fc410fefd1e8ceb414c6d248788caf560d2ac0ad92d6769d9511e4c26e952505126553e840753b635b5aaced9b27eee60eae83fdb913c97d01d3b18742f8cac1362ef731d0ca77f3b588ef85eb8e9602eb92d2741db5850f61c0c7af44fa8d0feefbc1c4cdf61fe34662f54380ea6142684f59451eb398683c92ee5c5ce65926bc26f30a42417e487707254dc0de6f4a01e414fe47c396d308712af949c1637b46c30e5f26cabde497554d7602ef99175749ad09dae1a8c15f2ede4aa4d83c1554d3b744e75bf2a1a4cb6276b6694d2190ce1fdb3da6454d2999ac16c273bb1d3da2799963218b6548c45eeac5a9742067308f7d913299733cd3118f48e6a68eda9d1498ac11ca3a5564444f52485c1b8bf3a218c12ffca83c1ec2a33c9d7835f3087d1989c9277f6f5d00ba67496ddf537ec82b943a56471bf37fa73c110e43a8bae2569aab30593aa5c6acbb916b43e2d9856de4ac50b1fba542e0b66493bdaa2164f8fecb060d2dd117ee296a13dbb82f9fd7d4dc65356305c081682f68668b55505832c95b774b0d4eb6a51c1289fdb838e9e29987405f54e5a42f8a9a5601c0b97d2f327319e8d8221ec98eec50be2270a0583fc1693b150d7957b8261262895b3a2bb677382b15286d787c97d3921c0164de0524a31dc24048fc96d593917534772ca703131c1f01f7cd7ff4b8c8ca00e2d1f3d72fc781360ed12cce129b5f78770d7955282794f3c8c1aab2c1ecaf15e77852d9260128b9ec3cb2a2de5500b4e0e317ec78f1d39c21043013f586070e0781e3b74a0068330610b24184290b1133674eeac901c373420811b473055face33d17b4fe81a070e1c595b18c1702da23f8914fe923415c11892d36be4082148fe8960d2185ffb4a6dfaa11d8239899e2e2513e286da72f04839c010a3579023478f1f770ad84208a6097ae6c42e547c82d01641505494afc69b50dac514fc02d073a0ecc748c17b0ef42ae01d5b00c11cd73b6c5f6559b7fd03d3ad09f16aaa4cbb5b1f9843bc8a3d3b49ea57d9a207f59d505555a905c2163cf8e2c85d512a4359c4d86207a6ca695fce3fae0363e71429ac7e69470ae5c01c4452eba52a7b75757060fa306a49dedb83c8e506e6318fa192d67bb6326d60104b699dc74499a9af06c65bd950cbda163430879295d729b7591873d3237fb04a0b59982df6c8cff1c3788612fb418b589842a827d1f5d3cf237b8316b030ec8e97522d95da93a3052ee82f8a85408b5718ae4a7890366695aae40a73fc6c4a72d23ffa766e85f1830822bb649315a6dc921b2e21e4ce3731ab16bc09418b5598527ef1d423fa4244548571c6cbcc63775cdb3faba0452acc9f44326dfd11265a04155699502dff259fc230a34cc88f2915d6f6358521cfe75628fdabb3b018b42885e1b2f7fe45f49a14a60e134cdfa828b1bb1c85395c5acf339eeac2afa2308eeabc48b9d3f45c96408b5098cbf7b44539edd65c3ba005284c6ef26974c3b49b7f72f470b51f68f109834ae9f4c7ab3bdd73da400b4f1854884aaa840a75e408038c318e560db4e88471945fb2244509a1d3ad43eb6c075a70c2a0539e74d5c5408b4d18f34c6507a1224fae980e2df5e2878f57c18f1d1eb8e1c58d09dcf02203ff0cf027560a2d3461485a232f5aeb3cef291346ff4ff2d292e80afd63c2d8e13c4493f1b86df94b98228994093bb2342b6f09e35616190d0f6779f74a9823e57a4fc152840739250c22e798a0746cd2567c12068b2fe5a2b327fd159284c93e0459f3ac643ae648987278ccbfdc25344b0b0963abb88b2ad13ec224223c789824bd76af23cc9293e571d1e3e6d1d2084352298e899810234cb225b65ef8ff7cf5220cc1de3d6878fc34a529c2d41d41df52b8c48f13618edf9cf3e01e57928830756d45cecb8d649b8fd721463b40145a1c82331591218c75d9794cbd2d43d742984f4e7b24091129e8130e1ca702e3342d0861c86be1ed3e9aa987e8200c9eda4f5f5032e2cd220883d67de9b3e471443c03610ae6e9df2cdc9ef65a00c2dce15762ebe133299482167f300735c1652e47889cd65a410b3f18ab2a64cbf1d64e418b3e18c55475b6b353d2adbe2868c10743f6d5135aaf5647655aecc1a464d632540c1145490a62c17e410b3d98735bcd2f2c678d11c181038c1cffc9c73f036e7871430c15ece80ef87813348f9ec03f0370e030841679305dd073aa53be893e3fb5c083219a051341854887568e1e583734d0801c28e8d1c3012df8b103f5f8e23980821e3d74941677305630154b295d314f5b1a9000056e7801868f1e490b3b1844ba9eb7904f7950578726d2a20e66f393d153a254c4d3820e26152bcd89acad2d212924d0620ec651423fc511f25da43f5e0c315a0b391894dc91266cdb93fe6c1c8c93bdd2e73c3e726d32410b3898c22ff57a7c9af0277f83f9f3072574bc3531be26410b3798472895eab3662bbc7c072dda60c8273227a94fdc15291b8c19f7a373b0aebd11d7609611f94295a67e75eed460f408c254fa9a4e923b70d0220d46afed30f6975375d7dc062dd0603e35ad382af94688941b5e9ca9418b3398f3fce5152dc2a4e792a1410b33183e524fde3199ddb6dff0a2c6a0451978ebedb2f2fca2f722d6879324a61406c3a7df8df5090c061dad4252b944ac17a52f18bf928e233a0891523679c11c93c4988bf6d1c9a62e1882c867852f53b2b4c40563e918931afbf3c1b40593d0f13e4a4584da5c0be6f68fa3a498c58aa32c9853d788d2794f941661c158ede37eef5e2a055dc1146e3c7ddd862419b28279fe4bcee8fbab6090e62616744c8da07e2a18c6624c2edca510d69f8249e95dfbe0f7e1ad7c29182e8cd26b23cf46463e0a269dc3c272fe50302825ce2b45f34bf67982414fc57b0f2a77dccf0906ad274fe85375555a134c3a8708593a449e5039130ca67f365b72eebefc124cd992ec7f8d9460f2fb5825314549307ad67a4a592349bc2024984ebd8268af9d8c0b3a827945a276cf858c609025eada84451e9b8b607899d4bee7fe612b870826bda2fd272c4dfed2108cade9ae2573fd742e0453ce2794d899dc9377108cb91fbcf64a290f910682d13d4552232d5e9fff03b345d5105bb99377da078664559354277c52ea8121db0753ab5f7d7df2c020bf4c84a8d7eff9db41e168020d1d9893105162890fd1b9710e4c49d32575c8180786a0bef44bd27e55a51b98c5f75b6e926c609291c44a50313fde5f03f3e59cc2e2072d0d1a9892febdd42a1b4feecec258b23c4cc55b656150fa21251593a4fea8b1304dcaa3c4228ba70b515898267accadcf6c0f7b7985b9f5f23ce8595d798a2b8c1d5b56afdee1e45b5a81865e532774921586144ab4d8d8f6e55856618e89be267a91fbb4a20af3e851a1c3ab877b2815860b8b93f20915d209156635792ab77592489f5318b28bb689acd3144679718fb91475bfcd52148e19a4308a57aa5b4a99fda65198440c69a1f2c6960e89c23041b25ef6271426cbb1333ec2a030669c5afbd04974c99f30fa6ac88868f93e56d0139d309abc4bbb3e274343e484410961a3da356ff2869b306875abfcbd5013e690f357055342849cbf4c60c268d13326a99cf025b74b9874a6c911fd99b6bb59c29c3c884d0d9d44866e953068cf9de81991db4246893c5e658859cbe973d608332661d69850df7d41a77b86244c29528f4d8e15260445c210f4225f277123f503095309b9b1a011b7acb247983d674f743d0f92e208f3a6c4d4fa485e426f84419bd7d68ff43f1f19610a2a75e53849c482482dc21c16f4eaaa9a84982bc2b45d1e5f553ba2a60dc68c44983be864e796e5647f1061ccf1ae60123dc40c4398a4e50e2b9ef6ed82448756a92f9a478f1d5785306fa530aa3bbb88b7b00eb531f80433086188a35ea4c596a7e8fa8c4118d5238f05bd386f396c8230c6cf5a85b6331046ddb018eaf3497f889e236df978318e0033006130d78bdc414e8964f78c3f184b4f765dd6f51f7df4d0f1638caebaa10109e01066f8a1cf4962e75e32ebeeae0f26ff537aa7b9a227e5834969f3ab1cc242f86e0f065552825f0c911ecca2cb528544d339964e1e8ca33e495dfef849780e1ef89a64415a4a4ab98b7807f36ee4135ac4b2f34d3b9cb25809e9fed5a190b6e4fd4513f25266c20c3a18c3b2c7cef924fe7c7330c90979b6b4b7f8596c861c8cfd97221d5ad21c8003c70d0d4860461ccce51546ce2819a2b28203b1cccd2a588879a787a524ad9c9b5e1f9b958f1f61a861c61bcc2985c91c15f2575ca243cbbfe8f1801ecc70c38c196d306a4989db7d2ada154d9bc106d35cccfb1625f2dfd83ab44831054090430033d6604e9e7447f850b711e42c84196a304ffe1cb23f3c92e7c98c3418ac74b22e11ef4907090e1c3870e0c0d13ec6e81968b825857dc81354b2a843abc78f349f7106f28e9bd05f1dcab34d3136c5c8007f608619cc55e925f731544a1a6ee086052430a30c85635730830c61cc188329d9c87e49d2728820d7a16580196230e4122a7867ee684e4a0312b8810307f9c18c3018dc3e3eafce25ffffc1601a197eeab79d2f18d5cab3d28cbf5759bc604caf53714dec9c2565170c22db079d2a642569c805b39fc6dd64b336ff666cc1f849747a74cf2942cecdd08249665859fe7e551b9314db8c2c98a45e557f927ce1546233b0608a10473cd89c55a490fb30e30ae6d0bb6a9f99f45d991d6658c114a6b3e73af9ecab38d36146154c492fe5d8ab6fa9a602e00c33a8608c518bfea5840ef2f41b5ef461c6149890b44e4c8f4f1b6648c19cfb2147496f414d4e6b46144c41656fa9b0154a07bd176640c1f81d7412499ea494ffc486194f30c49617d5dafbffa934cc7082714d09cf15d76b82a9b2e699fab0a4df2530cc6082f14e8c89bea518c2f59fb1044338d13a22ac2d82194a30e49cc6248884a01742c418a3c3c821c60e6e818f1fe30238706ca9624612cc1d63fb268b30155aa4438bc78b5139988104534ff28bb0358f1ea52aa0a5987104d35bffa7ab4776f1920e2d6bc40c23983f6ba86d9365b6629addd040038c0f338a505cbad89e2b29443045ba2472382192a537cd1882d92c9fdbe4dc0d2f1e8f304308269b3def5c5d62b456403023082691a2f6243523449c3e030826554b428a89084984cdc630e307e69e113ad9dd7c6c4fe1c0e163475b400c31ca15337c60f252b1fc3ea558e961a1193d307bae9945ce7b29f97c43030df8b1438c1d1cf02f7e070372e4c08103078e1cc452318307c61256722a5f2f57590670e0d0e2cac38c1d98b30891f4f3bb622dae03935cbe4b672aa8bab6941939305b75bf07ada2c2b636030706a132421223bfb43a35e306667349d739445fec6e0d98610393ce7b9769840ffd7a0d8cdafa63914eab9bf6cca081b176db364e2779aee82ccc4946ceed8dff08a693210be3c559172177413bc5636194f38e2b278260613cd7ce31d2e39da6fc0a94aef8fb9083ee09325c61ec7cf23a3ce4e6faf60919ad30658f9c96142537bcc0218315a6999073e4ea910e2dac406291b10a83acb7549adadb2e4a2f9cc7175ff4c0814355610e42c7f149e6e9d022960ae3d9e892a3639f305161167db7186b61daa7c5ba828c53a83e76aa4445047bf4d091e30bdf61860719a630480ecbb94eaf2c4e2cc8288539629c6e8f16d7a1556490c22017a9b3e4725bb3d50b1d3f7674408c2fbe48c00d2f6edcf0e286173732a0821d482a4761b4b2a0c4e7876c42045114a694dd43540f391446c99e647fdc49ffa2224106284c21e662c4c5f809835ac8ba30df19d5db138631b7ce412e44cd8e77c214ea1be1229987105e4e98f3660469bb262f4b043761883ef239a424e652fe6568c2142ff2b9cda92492ac3361082a4ac7896b1f3552ca06199830697eafa59438bb9294a606199730a91472393fec32ce34c8b044f1e1b533934483828c4a18238454562a4d2a7d6f0b3228619654df9fb3f6c4c93f13644cc2f8a3a299ac1b99f2519c820c491874685132e6237f098b181d0973ce9582ba7e114ae78a9546410624cc397b4fd01e27be82e8112699a49fadf493490871842927c7aa30c16b84c14d9db814132e7d106530c294535055f14347c78f1d86b508638cfe6aa8091b1f725610321461ce0f95cc278be534aa1f850f321261fe380badf7269a21727490810843184f1b2925ed8dd172d4231e3d78941b641cc268b5a293fe9b0b13711c38725496187a906108d38eb85162e96154f40b6152295b596f48ca1631214cb9ae944ad4532bba0ec2587b29b2abc3e5944710864b89912bab2d4c326504c2f4a22599c908353f512b62c8008429b279e9cf130bad5518eda3037f307fbcc4f8bbec74effd603aad350b31e42751ea53c1a9c0fa60ca29fbba878f947416e6833182f7972a9d2e9550edc16821bbca7e9c6fab8f1e4c25f78456ee4aa3cc9307e3bac82d51425b3e7bf160f4bcdd799797aa454887965a828c3b140e956107930862593b5adbd529ad83412e3dbca9cedc7a4a87961264d0c174f165a636deee1fcfc1b4e1bed6d1dcc2a5580e46577d8bd955da3ed70641461ccce715a247e4a685d02e1c4c55791b3176bf2ac7379852f2527d13dbb4c99d4508196e309d5e9494c3855cefa71b1a90c08dbd3bc8688329447789134a847ab4d860086e369643aabf20630d06d3ab164fc505196a3099c8113b2ed7686d4f122bc84883410909e95208299ee98c06438af4a9e4d9a4d39f9d808c339894ead8ef3956e7b0350f1e0fd811460f0dfc1829d8c163033a7eece8000e1c26e0b1e3c7183d7ce0c0e1e3cd60d2a3eb391e2b88e43c92510693a84fead6b1db316e9c1ce3c60d2f6edcb00006bcb8e10fc081c30212b8e1c50d1c3878f4d8f1c3021240934106938f29edb916513c3e7c7c8e301a0c6519633045fefc7471afa4fd9f500632c460b4cb712409157eb5727561307b2ed91a592343c74d9001066307ffb4506da3723e468f93429bb04cc1e05993432f220593fe892ef1588a8249f642d5758a9ccf74a5810d2818a2de256dc154a594631b4f3005a57b2c7b6ddca897c70e1b4e3098fc29ab0fbb8d26186dfc4ae297a7581d31c138c1db5358a99760fcce5e5bb335a244af049338bddfd5585127e6eff0a101fc612309a638a722e85cd9ab944782c1f34f5a374f5727130a8020070f1b4730a7cab6b09775c17392114cef1527b6adbc2b45304a94c97fcadf3ab8fb3688602abdb1e2794255bd3504f3e587cf0895297a9484404e9e93b9a52de8e1e38718ef6a413067567ff6a9708fbdb16047e206d80082c14c9427b359316dfd07e6d841c7eb493aa77d30bdb0e103b2840bbabeee8139b57c643fa52b6b877860ccddfdb1dc9dd424ed173d72bc0f2d0bd8d84127529410baad723d2e810d1d184bdf4bcafde1937e6e47056ce4408b91f76534ecb64f3858dbf7c4247bfa8de8d0b27103636c97c43711d361c30668966f7687168997ad816b7fb927a6c2290bd9a081297c7cb88aafcec254c12dd8cee994e654b230aca82c51ecd4669f3c16a60d153a9fcaa8e4e8ead022b030eb6eea878c32a52ee815a6d0f31c952fe474dc1506e595e553fcd22263b6c254617977d262d7299d1536a0062b4cebc95a553e45e598afc2a44ace6304f56872d6508539f7be5498e3cef87d4950117c820a7399d5968aa4e4f97fa7305c482747e5689bc254a1bc8436292985294d8638b7ecadaa954981d611a7fb4dfd3e7ae4781e3bbee0b1036b8cc260f27241e59cebe324573fc600831950431426a554cbdbda6d49d2c9e1a3478e15b48fac5098449b2559417e50183b55de587f3491c4fd845172f04962d259a41250c31326d931a25cbb69c4c0b8ea8441cc8b2c112972501d7528278c3f27a95626a55d52f5f8811da8b1098330f93a3bbaefc953ec050a50ffc8808f0514d384495f859c773f972ad5eff0f183003532611cd7ea1ca93e4eaec7043530611222561a3d218a0ee9f4eef145a97109e3bce87a90fb31a1a52d4b1872cab57e1debd2fe54e2501734e4828a52c2202254949b8ee63ba3496422ea537c477bbcf0a22c06c00a3524916d5aebe54b8e732a913067e6ff08d9134aa4051f011d62b403bc68818f1e60fc04b4f8a106240cca6544b8bc9fee0f070e1c38321f61489dd536d4e8c7d1d611a58fa79c92295d75a0f6a21185a30623cc1e3d7cc5c9c832a754eab250631124255f215b96861a8a30d525b3b150154a525e65093512619c9c3b251135ddc583a48a500311467def2c2acd64fbaa09077e8cd1a3052d0122d43884e15d2df4ed1a400d4318a28ef2583a7cc84a6f214c979753d985d850ca25844195d0124b2db509f90dc27026d2fda739a5c65382307e8e1df2fde7304ae8cf63053d72f4f841017cefc103b74620cce31b26522701c22cf6f15a2caf1c618c9103053d7a7ca176811b5e707d515450e30f66dd3adb4a7e5af22460ecd021068effe2f40f151004410d3fe81d3f89f5b20f47b90f1f175d7664e4033162ce45313ffdbfa0c61e4c2f1f2f4d5d7655317f50430f4813f51a9d9da72b561298c016dba0461e8c966a67cd2f88ded58d900703ac6ae0c1ec17c9b2ea5ef6e756f650e30ee6de8a92fa849957bedb50c30ea68ed6692d8cb6e4bdcdb8461d4cf2fd3cf578e5a778c3c8f1a3ad6bd0c158135b4e4c3a8d144e1d5a24a83107c3aae5a0b4940e22a6c5e460f612e27954362f754b183b5020461c0c3bb6292a4cb669da3ab448a971a0061c8ce731474f68fe88f9cb1b0c226f259554861ca99230d47083210825c67334f39c7bc951040b35da60f6dc972da4654922a86f6840023770e0b8420d3698fe947fb8e5939aa51346bf036e7871e35db0c3046d811a6b307b2959331d961a6a2852bc53322aee23a635d260ecfc9a10b4b607a88186f5b4ee44cf609693263e27a3066a98c13462165e91b70bd42883b1b3c8f458a16f3984a506194c39e364e9143d8c4e2a5553630c069d464e87b42a492aa544a08618cc22b2562276c4132daa11863c755885cfb2256ea006180c723e399af652ef59d7a1c563051d8618955f30bae7647617494504690d2f184ee879b40ff69d1ee9f183c70e52d70573c9897cd6cb95c4a23a34470d2e98f284247b47f6c47d7e0c3072fc185fb40ad05250630b85cf95bce59a5c122b6b68c1f8396f94d0398f1dedc38b478d2c982e549ed9f54bea3f8885add37cae31bffdd2bfd8a1c30b1c382c20811bc400a0831a5738a5895421924e1303a386154cf9e2b7da8b5fd4a88227bf4274f79c1692a840907fe1fe6905a1c6140cb2f44d92a87f22844f0a06553a9f08a92395582b0ac6ff9ccfc9be46574728a8018582bbc5d6a83c411b396216d2ac091daae104a3f5a589a182e59ca46f8231d26d8b2993ef9dfa13d4608241fefe856ab88b1a4feab2a3c6128c926e945e9db80e2d1fff83c72bc19052249720440c912c9b6a24c110fef6e4a988842bf3336bc289cd521d0c6a1cc12445251d2392fa358cd099b04bba222d656419d42842fa5c5e539474bd356b10c160523664a81c77d6f7342daf310443163b7d3a89137e419aa330a186104c1e26e88533fd7fff8aa046100c3a98ca76aeb14f5a0404237af6783a45ba3ca2c60f5049693e7aa41c39c478efc1c37e8c510107e0c0518a1a3e30e58aed263f6ef5942c871a3d3088912d572a44b20d3578e027f17493dfabb103735ced77a8990a57971a3a3087943f5458c9a73ad960e4c033d4c881d922fda928fd19e1e339c46852622440af060e0c179e3f9b87d00b356e600c0b4a4793f74f178cd72ed4b0812949df1d6d6abd638735307d8f4af9ae4e09c9162ed4a081b947c50e2a09a15e4e3486037488d10ed02146ebf8a27d3c18af230c830347de2c8c212c52775bd29fe6781e3dca6461f6bfdc2a7b3ba1b5d6520004390a402316e634e194a7240b18edc3470212e03c3e011cf8e28b0623035f7cd160dc6981062c9c9c73ae5675a0463edec7991368bc829c3346992e9bff920e2d41d07085e94fa7cb2e73db772740a31526954ed5a893af0e2d14fc8b81ac30975c18d7ac6c17dbe34a0110e470008d551872cbf9dd4fc8e621c80c345461521f4f24d1e5a9fb16038d54e4a73921c9ca5ec1da916305ed637d40031566af12a7d3acfb76ad5314219f09917c6cae44caf0950a020d53986754b9e874fb90d26c5da0510a1bd02085d1c3a5df13ad9fafd71f6f82bda18106fc0e1f1a50c002c0a0310a73c8dcd2a33c2d99ceac1c804301bfc3870670e05851983aa66d7fa5a450183365c2c5abda9af280c2f471d7bf72752539190e1c9f30296d4a694790ed1cce22030d4f18d47cc5778e36a94d3c061a9dc03d4dcb2ab897881c6d25f6f5d161bc8f30d48b0caca031b0464c063438613c51266e4d45a408343661f059f5a04bbec4451b8940431366fdca7dcf7163d2c45a9076fc48cb8471cf52d4fa6c75685565810626cc394bde3e1124df098b0b342e61ca2effeda87ba2758e1e89c7abe07d9c95818625cc2112c4e8add7d27d791f581a584163a06c05ed4302342a61ac5ac92a172f57e93f0337bcd021463b602d8c06e3980220c801011a943097cb9588a6ddb144476312e6cf27c9ecbd52aa3449c258d9fcadae258ae552240c97826765dfaf58c9878479cfe244ae6f8d6efd11260bb3fc68e97e270539c2a0c2c9bbde11a722db8d30a8a042f3fd466ec66684499a276d62b45e84e152d85af913492d164518ab5256a7f510c2f3980853a5892343a99e5069448439dae8ee0f3d514be50e619e496272a2ae6e9e9a28a061086388fbcb56915408d515c2e8b3d55797231f4a8430e90a7d4a6245cf6ab11c5961f4040c01340661fadcfe97453541d457f24f4ad8888f1e583f7818b3301a8c3b340291eee4654ad51bd00084d1f8c37d4a45be7ce7eb29411a7eb0e3c8a4cbec35097a0660e086d51a68f4c1543ae5d3c9084a528ef1d841830f26c9a52dafa6dede227c008d3d98e2c76c46ea91e2f1530ca0a107739e144c04a1b742d7d963051a7814f4170a1003078e1e87041a793049c8f1b3766944091d1d8a85a583061ecc3979b476ed088d3b9852d4515d794b72cbf3020d3b98279e14714d6fc9b9a4071a7530b9a5ad7f38f921beb581061d8c2a2a74a9eed892b3c6061a73305c5e5910e97477d2b71c4c171a274aac2ecfaf71308c0afe156a42e868270d389874aadd129d722a914f1a6f30dadedaa7ce6621f97783f944333d7679d2f15687968e80461b0c13b52eeeb7a738f5d16083492d788ed12372f2d3d660caa1dad4472d1a6a308e8ef20e1d9d278eca6909288d34182b9dcbe98d243ddd8206e3298b35e22e2bb682ce60ee971ce6eb7ed5e5651c689801d1b32e4abd153c8f2a1c380a216894c12cd1265c96d16c097fdff0e28617371270430312d8000e1c8767a0410693b8ec89f82989bff60109783076fc00038d3118f4497df3eca81da72406932475e26830f6769a9c7412f14e5ece60b42f29625732e24ece0ca690b29f71225e7dca3298538de796bc351a9223836947663cce047dcbec6e0c66f118ad58b1c6d08518cc7e215b5b1669921a06431a4b1f8408263018d62c249e2a1171dd3318baf8824956f4641db7a0dcf35e305f8a914709e5915474bbd005170c237a3b692451af276aa18b2d987e4f462959d182297479da6e87859c9845e8220b8b09392aa6a89073ce16bac082f1d4af0879c1fc2cd2573055f80ab25348ce1a522274618552b8d8ae4a51726433b79817a5e73d5cf9f758e8a20a260bd62772b96fb1bc2074410553f9dcde958974895ee2144ceb63d944d8fea04d772705a3c987dbeff93dd7d754605130889435bb6bcbda4edf0514cce27aeaf489a4271845899c62891482e53b39c1a89e7a4177a834e23522852e9a6048df1a263c8fd08f6d0a5d30c160113d89b68c4d7fd112cc23ef849a70093a59100a5d28c1f8923ccce8709554971908ba48823164084b62941a9b5ce1c08103075617ba4082d14aa43eaf12d94545f108c60c791d215e756104d3cb7b5aaee9b5ac1626745104a3e7526aff3c448fe5218239d889a4df7a1e82c9652497484229953a672ac8612ac8c15d08c1a85a21e1430a6f0bba0882c13da478c976ba501110cc622162440941a5f6f507864faddef20e2654ba74e103a38eceb994be3b11d6d403739bead57c51fe154d3c30e9f718bb8dd4c9837660d26ef7163e483b9f5f0786f9911797eae7c0d8ea4959367d5b36e3c094ad73775ec46f609e28b271fab781d1940a69e369c452e7d4c09825dc93c7bfceb4ba0b1a98f4c94792772be22fb3309d1615d5aaf2e91861599824e989d99525457f636196cbf20c4b49bd220b0b739c5dd3d77b798571267bcc15062194c9b8aea6c50bb6c21cefd2c87cfc5861fa709395f3534eddd92a4c32940911e53a92bda50a934ae192d8a79718499b032e5261f4d06149e9adfab81415c619cd0eff5449a5e49dc2ac7a1ed29ac8712757cc8529cc21f69d3aad3ad1e7be808b5298d2bec73f5f8c49f196643515df744b0110e4500117a330b748f068c96747b72c463117a2308499b9cbe9e4a1302479399e5bec10e3d4a030f929799f438ff64ee94f984fe6a9b394145722f48439554ab1546676c2103c54f0d4f1d3a4cb09a35790b359d242ca966dc210a48548d9b3ab54b65613c6fd8ac9219b7afd138d910963c88b31c9fd8409f38920b27b07a13ae87e09431ed7b81153f13a792c61ae9336412cecc70f57c2a85bee4946357e46a684792dcca8d4900f6fdf240c2e1639b6ed250953ca3946a56ce1f3ad2412e6544a7c72d45232250b09c38e8f78d1ad5dfbeb234caa2bfd848d312174ea087388ada43b5a8de494d208a3eea8e7aa161961bc58790f134afc9db508639f9d56c826449e9ae8e14211c6f288b5716e39592425c264712ed6b79c4e297b106174f708fba52fcb5cf210668d1c52ce25cf10a653b1df26e4b417c285306788701b7ad2955eb92084397afed5cf16e71f498330eea5d39bd7e9f17358102639efc17d74ee4f6803619212948a15f92cc4a880305b74499252292df7297f30484af36b9bd30fc60ee27127680fbd2df6c1182284042149e3734fb8e083714e9c1cbfb0ead0dc432f2adcdb2c25055ce8c170156fd4a98c502ae7c1d4c94b5faf96af248bc78efef13e481d800b3c9827f887ca8cd0154ca8035cdcc1d8d559bb2a516bcdb68351a469aa44d3c147c975684d7bd221f62d74307d4afa82789c6429b23918d7dfbb3c3f85f66cc9c1fc6a2e323c290e868b6b11c994ae3093c0c174226d85dcd8d688578716c7808b376c23513bf54d0660e0866d10b8704351f5730ca582d68b29b6e1d8503812176bc84f3b07997b22cf05065ca8c19c15c53b981ef352b948c371818633983decb53cffe80dadf2e0c20c65e0820ce68d98135cac432ef975687106b818031762281c18e0220c25792247b780551bb800037a629f4ab22faefb8239bf54e948d0293eacce85170c07175d30baa756a8ffd38ef51817cc17e14ff6bfc52e9d70b105a38794bb9e4ba405439e90440eb29e2c982de65dbcfe0b93ca830573fd88f80aa6129d645f1172288dad60ee54c2f3daaaa8986e158cafe29e477dbc934ea9600c8b1a663a2a533057fc701fa6c4cc868a140ce2d2d2ccfc1905a3e59517b97c49de07a16052e1f465316d7982719405afceb52ab19b130c9e53d5bfee4fcaba0986a433d573ae3b7ab4b86082b94ca99f90de5f8249bfb25ea7da9e7b4809c6fa3ced2784f810469d0473a4f86f3a799f4a949160fa2064e7a0bd83be6f4730ede6967ace13d721c58511cc77a72bc764cc45118c0b2218bc6b573dfcfd2c7b968b2198c35eec8a33cfe9b2bc75e04208e67893f3ce635ce93c291ab80882d92b299d929a4b3f1ed601174030598821a394ce5ebef20f0c93948af390fcedef5e0eb8f08129eb26754851990978ecb8e1c50df4a32770c38b1b3888e1808b1e1824c44f4fba2d073dd31909b8e08129e9285ff2ed4e52f8eac0c50e0c672a764e49fd3a30bddbfc05a5e7c090534e77eaf6ff727e70602ae927c54267d50a3937304ad239b6599738eddb06061541c83e59f9ffb9b9a88149ae7bbed694a0b4ca5cd0c0f4e3ff9f575eab559d8541e6df95672a0b8327557af26b5c8dcac5c2705176ee2e8385e9fe4f6419ed10f373b8020ab0f10a539dec59e7fac58edb1526b5ec7a5e210551e96d85d1934cd35d174195b4b0c29ce3e933da3c26a5e02a4c71db9252e496111d55984e2bb9ba488bf7a5c220397409e95a4187104385f1f25772d01f611faf531852a98d105b21bba6c914c69ba472309dbac472bc14c68a687be31f77ec6a5218fff4c66c844effd78dc21425988cdf505a73335118accd2c34dd0a8549750e1a4a85adcff10185d1447d1c4fd9d1fec4363e61b4937da7bb69199e270c3a7bfad4e2419212f94e9863a8ce06274ca7674936376468d7c6264c2145b2d86225b7c3c786264cfa797cf7bfbb53884fb09109574434ab1cb53ddbc08459edb292240faa335b1f3d6e780106f2800570e0402c281b9730493a9597b98f258c22ff734a1b63f2835a09b36f29214feee83115a2432b058f9e035afe60831226f1523282aa1c361e96828d4998d3e8f8a444885f22846c48c23cb9245d58b687534148b01109c3d59c641b0f3944cc06248cf6a5effa3b7ca6866c3cc2d49e2fd684485dc086234c4a9f14217374e5d0fe4d7029b0d10893d0aa8bfb693b87d57a78f5b0c10883b0c8de651fb63e692810a327c017051b8b30fa25df09494b844abb0d4518bb4cbf77457c885c6d24c2181e3cfc9dd4309f30220c9e93f0121a56af351ec214a49a16a5661f0c3084e14347a4aacfc536dd033210c618fe0bf00f630c7fe7828d4298a44dca4e77499d5cab43ab7afce0f1000da00f2303387078516c10c22ca3e457bed69860631086f8492b7f88916b5b2208c3df25f716217ecd4f360261d4ca6142994f1821260ad8008451764d48bea0cfc6471b7f3065fadb6e79aa84c1861f8e204f46e54c57d1c7f3e0b1237f60a30fe6af93277252a95492bc8e60830fe69195543a112dee771bf8d15f3000070e1effe3477f918a60630f0679baf37934c5d2c46ce8c158ea259aadfd44af78d9c8832917dc2359aa4a7a4ad99d69db2fdb47d9c08349e5d592488bef6034b5b5bc30aa938ed20ec64f39443a15456997570773ac65ff7277f3942345041b74305ccb5958fa768e31cdc1282a96eed6658ba54a7230ffe6e7cb895e39f58f83b9826ffae532eb4b3f1c4c724aa9da9b90635efe0de67c237de28f2e7929ef06e3bfc90fafdbb5c1f4a5b26548f2cf2284d8608a286a4ec70b21cee5acc19cbd7f42e85822df78d4609e09e7c9f39bf4de4e1a0c263b5cccea4457d3a2c1a0ecc7b44cd4c7dc3d83293c8fe4b524c7fa5c3398fdc476bcd1b50c267579bc275e8e98aa643068a5d293214b8aa59431984fc9180b693907991531983c5bc7cf8c4db41cc3600cadaee31b1ff252080643c82942bce4ef0ba6ce9e161f23e8657c5e30f99ec452ffaba6dd75c1704ac5da7d23a67cb86092cb3aa3feb52d98cdbf3c5592acee6b5a3057c8dea698d887ccb260ce93375b693145898d5e0585061b5830e828e163e59b5dd05ec114b4355594a79d39cb0a26212b4fd9b5dfa57c150cbe3d2a053d51ca83a8601a917d7af653018f54a894d025335924100743c15010c4403cbc6d13130000100c1a92c662c17848268ccb0f148003543e2a523e2a1e262018890503e140180a8501e180180c06034261602806c349a28751d20310f8b499e19b9d6bac49a2423657152f5569d5e3c380512ce05266d1bddd8312c362bec94777db72661ea541b2ac36b3d306f1af80a70ef03dba974a6be65752e034a571120bae82e15cbf6eb5e1d18b8b7d6154f19942a700305298cf2e3415a7267fb2961ce7e5e56f2d1d53f4edabb0d3e32e066c7a12232aab4beb137361bbf130dcf1390fe5319d83bdd9537b6025b91e92be782e07ec432bf114e140259fd046b6fab2a22f3a7e06f1d24f435a69f112f44f9e7f906520d8e9a5a2dc07813d937072cc0fd3199897d87328dfce1f6e1f6a861254fc2ed413e7c7a5e1c5229016c2f828b1a9a733b70de650a90d572f6c73a196b17fb05c8db0e6402a0bd6f05b872ca89b00dbddffd5b68524d40cb97695066edb939aa4f00a09e6f1c10a9c856e64312afc5e1adb219956da22a50ac3a67a477f99a43a9ef7178bd5d75a361e08a77379d190bd810b622ff7c671dc052494eeca73c9adc552ebbec6368d3918eca3e642911cc289dfd627fece88620e53aa2f87fad6bf6fc919121d98234ff8b1515ef24a33fae3c07196c2ff76baac70d578fe92040146a16e358123ef8df6c684c0b16e420346674d8dcb9ac45af489a6189a4832f5a8bc3bb76ce929b07e326f372bf3f21f455e5b6b0290e1eec65e6cc5fe8d645439538fa8c1465ad14a6bf2c1620a41ae8c6a00553950493d2386564935d5e9a8f2cd06d5b1965aa59a6729631f7807769268858738a4c4a6dda019a84a0ef5e459815517ed0d05ac5346a7cd294869dee33e01d11e8d836296db82495eddec12b10e683e3deb459a05b37ba42c79b44f1da27cbaeabbc54e4706099767991557a93dee0a07a70d058ff86ec48bb1acb54a39af575cfe598f2d6d61e9cdc773081d13b51c68340462a8c5bcfc611f15eb58c8c7ad925e962e938f58e5a376f9231bdf712310c02cb86e384438b5385bc9284f3d7db9d73445c3b40898941cf78d6ce1ffa37fb214c5de568c3dfca68a7db66adf664c7e84dbfeb905cd1ce96a5264ca05191612a54651311f061b891055b591c0fb2200155011b0ce43134029fb3f005028019445046f5705712b21be2accf6e52206e0ef8e38d9fc78937c467586bbe0497c57751f58f907c76d3a61d3349ba66dce9ccdd8df06e383cf6dc2c566c6aeab5ea9c7aca650ead4c37cb60b1a56dbd09bae6198241209692381d0b761954cd2fc2b6a478d8483db0edddddf3f0b8d5279a71f7b3a4c1a3f3a0d52d0029b5bff1a4a6fbbcbc2a5b3d15cf7261daa45536d6427c256a4b7b4f2eb147e3e71b59535edfae4bf4b5256c21d7a79e966e933983921f1be0ec1e111ea3cae8132e18f5b8181c0b5687972090bd9c5d9722346b2be794fc7c3879008da71cd91f6564c9258db5f40b58bc9d26bab985f46598d0cd6854fdb0ee72bb560595aedb7cf488a53d5bbef2a852d0673b671f076a6c4af980a645b4e8c863a0e96640a64c78e2232e174434370784c61e35f96174dcb54f7a823385adcc81a612f4636a9143b54cf050cbb38a062ba61c1648392636b97c5e2972ee0fe25c01c0f4e2c871d25bc73538d344e653042bf476ed8d971368b34e5739ad260e9988434bd9beec966fa7b9b0c62ec605e991c665ebc3901172e8e862f602ac89cad4f46647d5be043e51074a76d8b08fbb52c80d6af0c78cfa4e3b1a4db49896b0afb556af762711aceeb0a45e94a8bbce25085ca965cf59432af3a11bd7823d4965a751b7b915db0c63c52c9dd869b642d9f5051b4468b1ba404c19c80e2d28b0df68d3b440ed19d21d3239d64c90fa8832d2566e7d55f9715a9346e58b23f6727f5a6b5e0ff2e6789c3bb9a60037b1526127773261668a3aae671275fcb71fa47779967121f1a97b2cae77456676ebc1b4639a50153a22b0c8cd0b27072ea0a64a430036c5b451c4766100855e6269830bc00c351cc090e892760078d2905276bb91950c0065c91ce663266ddd5931585a07a827432558fb4464a4f3a4181466333a5b0b810d0993edf6e0a9a2a40020e3184b2cf5184f54e37f76ad388e3b72bbcd47461b339d8a51a92d097d5ae450fa4e32c8d36a703c96e61a3298c4c61047d23298b4e5b44b48fe64ad9b34c2023813d8e76f522c30c3752d77ddefc93a7ddc199c30fddc4a2a7bd1038e4e05ac166817c02aae06052aa4294396ab6965b5736b5590281e384a690516894f2028399cd7d0bbea4106db01981bd891e648a7a2aea9ad87e40d7bea36fdccf3217bc0e994ff290e0c0cb60c941029a8e06e238a0e9665d101bdadd546d0baa4a293528038af25c73199f45655bf1d8734a5e060cb55aac4f0d1b93ca924d2cd78230eefcf001b51970671fdc72535e08cfc99c72a66bd357a1745a4c7552e883c3c0abc67b343e457b3a22c29a61ee78c3a7efbf8fd3fdb37618088746246e03d1ec6607188179121cd9d28156b122a69c0952d52f7eda3c751c75b331d6f3b82953b7dfc41f57278c09e3bb74994b5cf36dd8189a35ca092b8893e22a3919e666ef430afe86b59c35b85e085fc03f85650062130cfd45e46cb653278e54f94e2ed4aa4f05897762437f48032d1f1266e014e5c341a77368f51855ad204e99c684388f7db0b9594800e090c6d145d8ef70a71c1bf8db19601e19819dd4b11f28fc2222da55eb04cd7489e900d649c0ed7e1178e75d505f6fe2f69afd762ae9577914d638f593f6cfac05dc0c71d36d4e0c156175f171a57c5272f426bdf93b6d5ae6b630bc42e51307f6ba7d72a704a90367b59fe738f3630d699e56d84d02da54b304f8498bb5334aae74845f6110cd0481c198e712298b37298657995ca0d4353af8f4926d114a3865065b4f7dc57afc1b4842bbab1c21dee2807495908c6fa3f6323fa7047fc0fcb0063399ea6d72277a0790dea6bd710c09e468ee9f3606679f76314f13bcd7ed0f2f714ece14dd65e85475e7a3d63b9d9af7194cf8f543d8300f202c8d68dd9aa20837c14b2bfb3c2904752d403a5387efe5252d7af1ccd30142e6008bf107ef3f65e697ee696e7c75f860cebff4b865a2ede1fd93a222f42c6720729d7ad59bdce495ee5d9ac7b0279b77aae28d70c11162ef21cbfba007a3679d772f8f8ba770fe3626ef665d2079aaf5f0f47cf3c2f25242bd09114008e5dfd3cbf3c52fb0091f1b315214a07b5479cec37b7a47802c5c01e668960647181d85101c078c47b833a25347e5e55895076977d9a081fb483a54a6f0c6051e965182688cdf9f89a98aa2bcf117908fef69de2acfa7bb66025efc100c2d46c03055452898b990ec20698ec4250e06126e23ddc76a7720822ac22ce6d1a6b915c2ebf4555fc69d4743dc39f9040664a7247476c848dc11dbec2386721713445d2b1f611a1dd78216409cc6e2d3da6df6a8aff2d1e4c3e59a1a2f7d143c2b35ebdfecaa10d690eebdf4c7c6a03218d74d0e53f6e227d72c070dd329421c9070f100083d2fed8df7a627a46a741a1b06d106be25b990de10ad8713de0c783a66b3fc6a20e90234318ae816cf0f02cccf8b5e6fde327459d8cabc8117f5c640ce521091494731dff2ae38892a89c34b9c21dbab4d999fb699b077f2562435f17d42a209a93cc046ede4c80cf046f6166945cab259d56a0a477a78a3e0b009fd23e219bb513fd462b9db0bcb2aae456ddc06dbd38ca260a65ac13b50fc71e6a08f16e6b81bcf426a3334d41c7f5524526bdb3c8430bd9c50ac2eebc7fc0b61aab2cc5e9c16ac485417bde40617814c905bc7bad1b9f93b30f705e8cdbcef093c25c19fad8d1885694a4df2615a5bd0544c481c611ca3d6dc08f0b1980fb1178412706435437b95314ff7ae0225e4f7f9a893d98c95460acfa0dedb129b3edcbdeb64e298d294fe9b9723734538d1a49a26543759017503da47c2f2e402c78d9ac5c721691f292ba2433df915acc2a5a652d435ff226f89ad82d4a878246f4ccc279c4be5ad07949204799bf328845ea2ad218cd4da172d75168e4ab2be4adf32f50a5fd6b15027a5db36b343c4db31edfcc996c89ea87c29689c58b48cf46b72312f416d11954b3626d98005af89cc2bc2ca34d7ec882407214ba12a8773f95dbc1c0ca646295a83f76c6c3019e03854150e5ba115607a56b05ea97711bdc3d805482284d5e74ff2abe116eab0e4a943b9351d39e41c3f29f6456b105507681b5d6a5f780919b977483365040d368855885f2ab46e5ac8f29e11e7f324bc82a1f64ed01ec4f7f3888c665b34eb275bf068f9d3fa06b1e77dfe69222412dd15bdeb2b08739aefb9d10e5017405806fe1a3a6484b20b6776ac3db078a2fb7fae0be339fa5c6cd936e2ec390fa42de5cc339d5450dec9f586acc1206e6ff97819e2a30ca29a71be3afc18e036b83975a802f0ef03a90db6fca0b9864a36251679b5eed17f5f37fe0c8726cf9df2313c6a54460a39cd6467e4fcfc0296a78c692a0c47b825165097ffbb2a53de36693a049e6adc7523eca6737748601e4c35cc8033909ee502852ed838886048b2664f4744bd9d5809a486ba4def9cb6be5021e71775e544a7e060e8dcab300ff427501a1e39f0cb0b823e73dcc07eb714820a686ec76795ad0dea05105c66fba636caccede7c3a0863dd0688dbcf92de66f7b2c33d6f23dea12042173887d0b43dd1676850d0100dedd02b3139eca4105b64c168f2005514910058144e34780d62701c2c925bc30cd85fda2e93fe37b797630b91fbafab1181630e70e45ce4b7efdfec087fe31e5134387e865fc4e47773629c01e90f477414134466d0a98ad5a0ed8fc41e8c81eb162e068c4165027681f08d9a9e0e78e3f25000b2b0cc546cecca2da9eb81bea0d4dcacc814f073d9e8efad6e26dbbd7343421caea293fb2aabb615866d670049283ded9b6397c5df0c339c84a72e2e29ffbf6148522f74febf91f269490a1ce31902810967cb0c6508b81b068ec4b280bdd009301807fbddd0f84ce622a80f094dbd5868dcd214d55414f685bb6976eca808cd6c42e16d80ec3916596da24c31a731cbdc80aa51caf6d1114aab44604657cfb633ee1ba910b4d314304acf879c3498924d1dc7c48bd986307866a9c9e3991014dd752883c08c932831ac49eef518c87998a08240c44a27c596959d9b090c6056d8744f4c605e49f31c38fda7a3c62411750d95fe157748ce7c868c66cb1025121e0645dba5cd1a8b6a08c5b8cb5e3939d0adc4f15022c4c318065463dabafb62b9a8f0f20a7cbd44c86c0180b187ab22f98a7d35b4521367ac03732761288bdc36912a2bfb8d0c44b9d99d93406276b558cdee0d69990c94f0cbd866d6dae13d2e6ece317134f00e66fd08d0dfca7cb3d1ddeba20b62627799ce4547e1c99a62c6f1ea680f34adcf213c55cb5d5aab3497d90ac89d7fe461ff59fd3aa7994240d9860e9b4dcf59a8fed33825f8e4556d95135b69f7c046030c5b4c342ac20a11a5da9d601a45f1db42e503327c86ff24d5b0f97ce45d6545892926ceec0f441b11711f2d098024833dab0f26c43fd535aa41310754df2b0137f55ace97e0a2b113b52ceb98a6faf6a6835c2e0091ee48b0aac5f046a188e9447e8bcbc476914c73a9712d4e41b155cf2e1d82315efd519328c0a5528671198d360fb249c04db4c0f328a583a0528394b019ab63a8c017ff0864cdd3cf53b3ab76d5443dd1750cf806d81f3706580d1f107b29e0f37347909aa5e26ae36c1707c8cc1167feaa1834c25ffae7ab1411a07607604c2b5d978cdf51d03056e41e0ac6e3700ebff0fa2fd008f6497133f3edd116539f66d296f6cd25aa5a284d1e3b643541bb18812092ce7cadbdaca3189ab97a601317a12b3a3f29e47f338c950accd3e1079629d5dfcc40b06c87d014747413c83f02a41c59bae590fa251982e974e0a11451b1219c7a3c605dc7287253e29ec039d5127be398228ebb4c2297907c0587f366a7d3406589ed8b3ecce89df6803155098f5bd3e40692a99f0a0c4ea0ebb12a66175b8b488f1436761ba9336a2b80974cc89af07fe9cb0b53c976c588c12b69ce5bfbabd172994d4086b48f80ad80523d82f70a73028ef8dd542164bcd118a2685d650585cc11a8e57cd63926981851f3b0d49559d7234d3e61ff8f3859979309be98a9cddb78d2b571c5c622b12b2b151c6bea4adf23a42ff101393101ac4052628486987c0ec47df25532e40e9f931965e3b135a1c3f543752febad0d5bf38e6eaf8bd584a9d5a348bac32b5d5939ea50a29a97ddb46a2b52bcec65423495533e15c0c75f35593aab8ab1259031bb07671c8ace71996be3668de2c1a15305bbce4db594fd410d27c9cb89ec304139d9d168e2ade362a8107e8f9f3cbb63decdf4a23f875d88e9625839de55465783bc6d9f7c721e4dc844b60e3a09723da6e3c0a76a2476027ccc3b3ef13b8fe6448e15141e3320369b440fa52e344844a03218067a5a0f435026870307a8458508fb9e15dda2bd52d9dd6dcd2312606bb544680847e57d571d8e71ffcec94ae7dd6097a2cb3716a4fd90bf935960d9a44b175a60d794ad78b757836dc7e865baf5e4f531de949e70a1917932503588819d246b17e5b798d75078569fff3a30cc8c9c52f265bca384b3eb8aa02fbd3e85e110c92010321d72040ba517f729be6cc8fb743e4d429855278b501c285a453497fa9fdd91425f4ae34f18dcc0903842b425cbf862511985892c0464bf1bb2d5cba68f5972992f0992d9e50b04cf10dd7904942214bc8ac2a727dcb8740d15e0f3f14f24b5c88684c2413e847c330d2cb6948a060bc7894da9ec75dfc80ef3105a05d7e8020f30096af6297179d876c20aed32ddb4706c4181929e9118fd7411d03afed58e614760625bca38e3f60eb0cdc129e6c134c541d6934b4b52487d7d570b023d257901b82a8328338ecfeb2097148fdb0863e38820dd0a3f5b60b0af72e2b301a1e3c030be54dc341ec81c232920ae897ab12e9d037967090a55c0f033d0a2a7ff9945260d61f7bc5f0f08148706a80643ab38abc4510424c024c48804680f7263a0fd3906446323e5a6500f3dc922a6a32dbc0712c2e478ebe597a430cc7a4f29051a2040b4c88210155970d549a4e0e891ad23cda8a26a5a1f0a4d4028a32f9c0cdec6ab8505f2aed1d01742f20098753a60c990e6118092870418961c58ab965b706f4d7d3e854407eed61b33149e9457b2aa8e2c451f5eb4781488dbddd5a7193934fd0c159740075b7ac5492f841473d86d18d4c1b4e3d6692e6d0841934e7836c80792d548edd89a8c72e5a61c81412195b586063ed5247926e772f5e29300fced024a689d8623752c4899345af69b5e819af4b6add870d90e8632d4c1f87bd25163e99b7bbe57acd0e352e6a58e652af0660fe91e25fa0104632df225ee104899b219d7df565c78e5bf8e7274153dab03d1436760185990dd1f249dbafcab550b959b4351c3c318c638e2f3b48a415a546d222249ebd1496c4ae90efab227bacf0050cb8f1b59f2b4cf8c21ac9c6884756dc49db1c7d63347a110b2dd7c99e41cc686373898a7e4bf39b319222f43b5d2d9a7bcd364949940269f98602980cd64bc63a259d0935407463267a9f392da6a7a6aa5702eeaaac624efe27d360d4158b086e24ee243c3bb91499fb3dc7bdbf2d43c22d42377305136f0eaf1f2120615999034c798744bc8073a4ea92ac45118dbdc0109b51570c7aa8c68f7248ed8248d92644e4cd19f80d5424b78144392b80bd0a6a2fa26647cf958da8230d6a49751677144f6b8e0291f8b0e90446794ac942d30d04a1c7c138b5975183c9501e9b072e9917f4c88fd9e8d2bedbbd26f1cf42a1c1cf6dddd65478a22ed63431fb1a4d9746ed8096a03da7af7519ba4da9ffdca676ed187d92dc9dbfcc10004c8daf7aa631ebd253df0d88b622126d13db3dbb57c50eb6265d5cf16fd9062cce13f275b87e5abad6c7363112484409f79b518e957bbd1b205b0f7c16165db760f03df9c44057a28de50c200052136e6a6ebf7b5a1e2e527959345e9f1cce5d92c4e6755df175fad643a462aaef562b3d013bda9896a47fa054a80ee8bb84237d6200ae9482d1ead57c369e5aee13c2eac391aa268d22fc0938336a1aef967827095c2db4dca77fba0205af57055f27c7c32b7886d72bd513961c7a274909a428d7ae788e72661d24a9a9ad47d42177112fd570ba2cc62907940a6d989ee44efd47c69ff38da1e400f76f3b991171de02459eef153ad5c8d574cdc59e46c0fbbe4a3473b1ba4e326fd71b538f07ee2ed20cf112551efcaea61bcc99f856f8888bab78afda2bea0ea28436dfd2705efd2d503c32d95527640d46b809c69ec016019e6f0f79e0335861592e6b90bc1be1600123150ccbb6267e5d71af0ca5eff72a282acb556280d979746324e182b71b1782e422b2b72c0fcb2c7690b8bc47b08843c1af11074c7238d3caa689e69743b9435e87904126710f3795b4ac6f25ce0a66486e86d7c81618832acb52cdcf162c34c336aab183cf12846f1b5e4fe956f250d0ec437e55874e21058955ec5f22e89b03b1530ab68f1c4245883277d53224f2dba9372d4456a3be07cf98cc32c414f9d70f18d2de61bfbc6270528776d9542c12e1edc37c9df0d565c818bca6ccaa8f5467e49498398ac4b6b1663aae2f182b2b61e4b51a235f880ec5193976956cacf33ff4862d521f6903838aa32e590419315736936f442f74091e9312d1df638195a0a191f2752327efa417d1ae2f7276569be2b4e28deb8b4dd45dc121f9ba3d142850a635dd5654ee704d0e3a295bf69a7ed65fe79077a7cf3b76b318acb024097520d505fe5aec52d5c08d490cf4d613749a5383110aec14c901761cb056fc74f3abee5d20208798181aa3e28d39e4f18870e9c6d1d6cfb4782d672cbb3f8ce2b36cceb7201b7175266ce1bab62761aaac623042a67c49a5a2c1289f1faeed37ec88d57cb158018ceb9eb4d15f64fbfb4d4d64791de7432c0a1220851797cb4a632813b94f088bcbcac4abe06e610a4b1ec39659868bff25fb8de228e71f5ea540c7f8a1fb69a0550a18dcdadb8a0ba611635627746b45baf1620bea216b2613641ca3ceea465e215953679768696de92ecd38aebb2c1805bfd615c8657440b4ea5975566bf26d3cb19eb9eead9b91cacce90ec5c0771755250d1cd2fde39803648d81352e3dafb90a652d4b5af7c3190fc82873835b1a31d4a36a5d3093531ed615850b66aec3b5449423be54f17328e7a16127bcd1ad4cb304dce5955dd1d011d8d15d46ba4892a263dcd9be86562fc78151d32e491bb4e3b3a3596efa1344211afb9d5d0f205e69840f47f37e12e7fb7bf5735b8599d722ec8193241838f828799d5f5378e43d06aa7aa315f2b0185219ba08e563f82f4a50a32c8c6d7d48d45064b794a0c6d1264ccdb9712d39e33aedfc1d82c94a39701ef9f183c1239a03b012bc8cc3e8a40405cf5ac115898ff029d682197c7c349bf1cf9642497dce19a9cfe8a032e35b782efc8795741e9cb934ff07ed0358df888992134bf6d0b2d9feab05c67fd10dbd24d55148f0e825f673f3958282f7235e839dd1ee52e6d43623a557ce0152886f01bd65f309494ef4187deb2c6134db3649c5605c9fc5997bfe7c2f39f37f40b9a2ca0706008576e1c240dcab0280da226e0de697ee9a2de840bca9ac507894f1302b18c2de24bc77d8c0c3108289e403324fbc56888a750a0bfdc2a3c21c2f48a21bacd74e4fcda98033c5fa2daea57bf3af4f6c1e0aafd056c248f1beb48f17962e6a8113c22eeedb15997d2bc69467088dd4d7edcc519fb36d93fce07f13f53d32ea5ec781d7679f349ffe6490b0c1b3cd7cf822933b0383e639b145f578cc619fb98be6d55e373b4488441ce087bd78599ee1644ec7e0dc7c169c529e86cb4011698530e562910cedd840a9eb61849406b6a27b594cad486c57f50a67dbe456a9b7c1744e3e60d72891464770b7a50436b8115031fa227ab70170e960748fb05e9fc3fab51c78aee9d805654f19ac225807b5d3bf2fddc30e49361b576f11ab05ddaa854a816c04dc14376442d21657983b097d68c2324a21f2f0af304d378edd082e22c2609984ca96617e8c43cb5f1802cc2b350e0b4b0b768cac2f061b85437bddcef9c48f6fb9c6fc6b005d895fcb798bae8bb604d2a9fba5081c55cc11b96697ced4142a706945bafa111ad687215b4aa407aff3cd0fdf98cd56f5e8c7eb7768d63b16f20a5dd8f6d8a3951658b5ab48772a3876b633248872b2eb8341ba69489121f4354a4a9961a941da2a8246b2304d340ab1af105502eeaec973f5db55729d773430c14a00200766d175d1cc51d9768384495e319709320c6efe50e9acb1703ea79237cb6d7cb85ddfb6ba933498d8fa36f936ccaf8c85a82a28f214b04c0b419e444a856fb2fcf8c20641408e26a67f18c0a68132574c260b580b429d8740d074a3240bd0182641c8cea043e55bee357a798aae228a55e86924a1da2509a5ad0f7a243b81caeeb662cdb0357fc2a7feda430df1ac4c7d3716d680510d9faecbe152e64a8ddf65baafbf10c0ed442d02423ae63b17c88d630029d458782ddf224a28cfe7e9739e7fc5026640609806721612a47dc91e5a5d8f8375f4ff0eb186fb50000d4e5ba3af8a487ba8d46cfe65deb392ee628f30ab7c320b45a3724ad487c03fee0887a36a9d8f3950e84bdd6b802e01ae8e3f5459c73403e88624a0b3931eda800fb690dc0fbdc741d60d7de7b8f01031f8734b34041eaad2338dcd6cc544a2682ffe6e7c02613a8ffeec91fbf83cbfe75382f0dc6e7e9590eb0c961c6aaa6e78ec2760a0625548e59ba705b1c31f91595841541cefc2612ae76cb9a01f4cb38edff797470a414bdab7b28cd4441c5a960ecec11df7a017863bb7666d401e70918772c58269b6e7dd5ca4ba36e08d534df440d75f11400714bdc27af48093469a78e7c21f233bdc260b7ad3507622571f5b8c41429276582640c9348208ea080efae161ba1210237284fa6b4f19cf64d92aab1d6bba2bc4a0701a548d53fd997ee4505967a16ada03368236222bda00d6f961d33809153854b6a22f2ed447867a2151ab4842ee125be7705b4c5cfdc70a36d7d6244e20f9602f834194fe8c626881b6aa3335d7e000d767d3294ce945b7b38485db47aab142485dcbf70702649ee8cb84c3ac54bfacd2f309c5c6533dbece1d774ead1a857194239b07dcb14d97d3f416b7c942f1eca0b1d943889995dbb66878f50971be3a387e62325fe081ebc2f372a59043173e5669dfc34a61c7903c564eca80a04dda0d90276be186f89cf6f895e33638690d16d2e1df7b53ca0cbf1f707dbc078f38d171078c1cd8c759cb5914ba7711b6b3e5cbe1a404377a681b6e14bb2421751a8d908f3234766e34d2e834da4c88c3a9086818fc28d6e213a73025b0b4576be2602193e86e38c88c443e1b8fba8100e62aae84bd8e7af742d27f9ee1e680ef10d7f597532144be142e53d90f78c7f979c0fc1172d5ef066eeb71b60b7452b2fc6a87f81ed9defdc3418abc4bf9610162b2380c113f75143a4d85422a93b44e2d50ede6cf890b5b2d39f3844194d2e3f96c70907449568efe72549e923289dbf9f3068ede75ea5a07e051d54e91472efa8a006ef6894aa5f9704d1f536dabb8d6d7156e249b232b2d7e865b2f5a8c11d1e82805afc030a780c3a9696f4d88e8d81a7d77df019ba37ae6152715edd15144abe8c8e484c7ecfab0d624aeba8a939ce0fac1afb7465588012f91196172d62d10382635cdacb58af9a9b0b8a5a14955fdf0510b5a65324434d421abba887bc00f47ab805a85084e282f788b83dd9a64620358e0d7316590585d28e5f563bda56922a8749118364b8910204a1ab9c1e5e55647247abe63ef13d13413a667c63639d7c3394e65fa13de103b4f36255277c755d3fcc9ded573252b54eb13976bf39bab33d446edabd303d25d49e448120af5c23ae3b87720a337af52500d32341af8b8300c7c1815647a3974455f3b2836ee48ee558d1a6e596428c6df716f7f801efa11daf5aa33a4fe45705e395eae28cfeb29e4573bf14d5a6be3e8711f50c3eca66f37432b66e2c98423b4de95e9a61f18214ac8de1273149816185a5c3c797b877e41b0c96311f9187f0d1f4201b06e2520ca2187407381985e1b403bef07d9db6a45804fbf824c24bfe0454c733a71c8113e0961e1dd4cee765db8dd585f5cbc3d3e55b1a50a964a3a3e8a0164a4e74293746d2a7950728f8b38c7259c9dc62f78ff78ac9e632f6e6f0a8fa1db2508ab0c7bd85d7fc6260508106f8a2dbedd7edf159327d990b253a32c44051d2a41ecca4043048a974b0027acd2d736e67f4e5f0b9909b5a0f37cf440280e57ff2d5ed824300df6234d67832768308877416f5180f61c96a1b9a427f604402f3d031cf4315d01c62277514a8698b9476139a3e459ce0740ecc4e13f7351e46da49dfede9b6bf83b08f1e76d917e3a51c82dcb66dbb31958f3f086e70ab726f1db8115b77469a163fe3dc98c88814115c78551c011ec66da45ceefc07a8dc1fa724606c96d8020aa47e09d6bde8e3e29f99cf09d61fbe3fe2c0621a2f7c73e660c35f492775b6a13c4caf399d1e3070d11275c92497602d4eb05734730b76453e0033a1f31cde2c7b87989e81084cbb84b3a66d1d059f4892e2c6be2976db7b981bff440800246ac1062f9ae5ab8edaece3bba3e31683b14986dfee2d23e26b0390c5408c6c6b593a5b5b4bf9ced44ee174286f5a6ca21d9109b18984aea0c11f4bf091846d1db507c523aa42cb103fb2dd60c3ce030f0ebffd64cf129dddef80818efbd6506f053c4445c794677891ad3c9e864e7347fc1f456e3951a9d14d9a9be058b9f73947b65f1f6cd001c2b970d090bef270c52da18128f1c73c791a6fa3f31d2e527e6f3a3ffcb145c88899b3d04edb0214ca5ee5eb181a888d31141620493468e1b97430a0927150220925a16ca59ffe804af08c1ea12f706f7c463f379c3cb0af1311e356e25bd47eafd3a35fc8f0e6455847908c7ce7ea337c69c2b7cf1cd00648968acc669e0680438b9f92fba685ee09ffda9c4c21309dee92143d9772422fe51a1708a1ada5698503465f36cc13aa1d835f7379f7ce223f5c631538916940c440e375d22f1ec7897fe07cb80f40f5d7d8d3813694cc7729ba4299d01027f81ad0d6f3d628e412aea186e5508e8fa3071f2724cadb1d808baa27d20880d375c61090357f91da029953f424dc06f3efe744ecb7eb72f4fa150686d5407d326c4842ef146a0eff4852752c068e495487dca50a8e5b2bcc81612061d58de41baa3066642f10cecd173d0cf619d1b511915fe15800d0914ae75b7ad69591694ebd273d5cbe6680206c8884f9e41f948624aa762b88412dd3d99e1b11e76783effa23e5696002f44fa0494e94a5cf9b60ecebd5d9d64f0681b91ad0c9be5c58e4157020db5de81f06c2d76d3da574547aa0687a3913b69e088a97a1619651f5f2da1880d9aff817b8a33e16a709e255235692f8cd61dcbb2ca36b7e07d9b288506090a286a28924107c10306e14d2521402b54eb5142b2bf0cad02378cd271d4f3564dfca9c84ea1f5b6695892ca52498a72acaf16d1f24ea7247498e8bc09b1fe19477f45284a3d76648ac9d6b5fb28d265fda2cdce0977055da5f99dd39e0dc4821e21ee4b8aba6e03c9aa09f6c71f6cab0301bb565b7e20ee711dc7a849cca1b6266364375d6f045a57f55e74d21971778fcf4debb85671d42b6b0a146daa886d80ecbae7a51a3abd9b15b37832c988884f1820975d824c7d7cf71ad4af38f46befd91649141d65e30384181599e1b2543250d2cf37c8e0808f9a3813824f43a548bdb33b416c03eb8c98a12a8a21d54ee7632ed553a7bd84da1a6d41e3d42352ae15092c91bc743309fa4dc8fbc8ccc962949f7ecc45902e399633ae71a98b78ce48f9cc3599ff9731ef83f15529e040159491902bf1d5e1805c13aea94186e5bb0363dd61c9055574acfeb2a9a1d6ba80350f7f403b0748ef3df466d05c585d1ab4da3888cd48c66454306c9eb99a07295635523c9513759c9861ab25ac6e2d3ac4822f941e990764364a2bb81df79c055657f0a36fd9a8ec3020c0615c3747aa9fcbdf6e52c90c5d6074f6369e14c5d92d0c81f75acdbfbd5da7d8b49949152619d5ffd543ed5477aa76a8367807b7153eabbcdc2a01fb09da5cc46860cce790f2344625f524fc504e16595943c0f890ed161a89a037371011ba0a1f0f0e1184e3dc404c08353fae7c73f64f9aa0263da1138a6377b657e8f11eeef625f021a52f3fdd502cf02048d3c70ef42ba2c4ad48baa15f915f88e324dec2481080f6e2128ed1ff940ac5f412d89d693fc6cd38ff944b7f5a297b453dfa24aa326ed4e9aba97e72c8f48d346da6c1fe46c40f03ea21668870dadc160714499c8aa9e2f1d192d80a1d6165d471395b8b70af40d8f16cb3f27dd8165cafe11e089ffb06a5e11f3532fc512b2b36583f5f70cbda50894f97333a1473135c4ef4adb28e95dd8828a01b9d4812c91efb92026f71017c0ec004b8eb460391df501c329f4f9608d0b3ca342da857199e02d71d1d8f3ce3af7961f982c56ea62efcadaa629936ae0d94ab3726a0da893e9554233e8fd96f86918f98d9d416624a180092463d5597f391a4c234616d74828fc919f86fe6517321200988891736d76da16e7dba75e9be47971e5dbb75df4d976e5dd5e96eb6b4e7546f57b73fbaf42dbbbd74c4b034701721de6bb68bbebdaabbdebaf5f8386a81cea7e5f34f4bd1d732ce5662e4ba44defc8c2c1619dbb127f9d1156d912a8b382a13962fce0a1ca663dc3a5ad7d455b725d9c999638c0201353038ef1fe174ae0a317563ad7d6b9db98d9f2c424a8eee521b1c471730b30d1c9ecc04ea4095d5528989a6a133a4eedc2525f8d03d01c918954cfa4277092fbfbc68cb63838143b244d7a0fd1eb560b1e58c139d19ba49f2f254e286cfd42c6085b24c81cdcb65d78a1a6d47fd3049cab59c886aedcc9904447ce68aa98bf11bbae24db2134ad52327c04be2bfbdcc940653e8cb6577a26e53d282916c2a9af1faaf974ba9beda2e1f01c1d3261e702f53f6395046211f579201c3b6a893091d469b86b57c95488837a69cf84b38017e5fdfff7f2d5996ead893951120ed28cb083ba9b1162fc6628e805b4791f2e56dfeb21b5ba2bc2df9c30f7f0e67a417d37e9e19d252ec3b9f31c89723d5e75e4b2d49e85736253e3090558a4a505ab52292b8a1364d6e234645436825ff92ae0815101052406d5fc16bdf9a58d06ac7880bb791b18612a4aa64fedb6df61f05a751d944b93379ac178cfb1dd67bc42581d513eea04b25e1303d49a3cde3f4ad7dcea809780e473821667e2cc18238ce13020629eb8c2d8c8ac36deef0bf92599cdfa6797811d0e6bdb4d7eb68fd61d17604b8006781bccb5e5ad7c87d1e977677297828bd17608a2c37c122aa9cc7d4b8f6358aff73b490c06adda264aa02babb5b7e73a9808c4b8b84806c9b24b804da864ca9d93fb369406eae6845b622e7d5eb44070dd8e1974bbbd82934410812cd353bb909638689d11e15395487272806055a55717743284ccc017a5b27853070b4940e000c30c000030c30c000a811c068cbbdd901003441db006c4a4a4a8a1b17c34ad606c04204d64684c6c5740111088a08eb07ba67a01127375bffb5e871f44ea8a0a1459fdb9f1b52fa351ab6c52c1acd182219c3183da7b5852cfa890cf3c78c2c3ac61b046c118bc62c74fe47e4b895f1b0e8a17a8efe57cdd931f28a1ee5fc2ecd96e18a3ec4f1c63c34ed49af156d87cc91bdf8cd2503b66045ff185c94d2148fc99d55f4e92077a7f19f7cccaba2d90e19b224f3e81049451fc3587769d0985222a868520665e6a153eb75f0147dfecc1ffb28d43fe54cd1f67accaf7a6652cb4ad1c4841822a85f4e1749d1ac44c5909daa1a83a8b718451729e4603e7aa1008ba2c91ece73d60c14c1960c78052958c002104080048c60051430818fa051f02cf8c316a1e853c393c4c99bc75125822dedc016a0300b6cf189c653ee144692a58c99819ee884d241cf63b80a27ec6cb189365c8a7c1015ff93e8c7852d34d1447a7cc6282b3b8e99e853c51909513a4c7429c6fc29b8bb97e89159ebc330329567b444ab96730c21836a54a2efac12b9ec5aad1975c21694e892e38dd9ad934de4338926c4e48dfcf954743692d82212bde77448c6a1d3c5f2256c01897163b18a38913ea28b9396e3fb6a92613aa289ede338b998ef65f6a891f39f0dc262446b2da97b324216d1e588102b12b9a13bb78522baf8225912a721852d12d14aeede8f5d0d611f8e88ae716758cf980cd31c87682c446a06cf0925a9628836c7148b50113d32dd2c44e7fbc0273ea892499310cdbf4b947dfc960e7206d145cec6287c8cf078ad236c218836a5fcf499903a659203d1698e8862bea6c1720b883ea34e5afe383a3f86ffa1f5fe7cfc9fa1cea5e8875e4ece232e57ca167f1f9aefb290e52ce3f9cc7ce82c2d4af4f929cf921cc2167b6854e39f06f79f74c919610b3d34e12cb87b9660953a9e875624af7ccceb7ea07cb0051e9a9f8d7fbd9c71ce5c66618b3bb455d92f698a595a2272852decd066ef24eabe2957c8963a34293639b08a7e10424587be7189745f65af3ece0a5bcca18bfceb7d071233c62b87ce71b82c9a2118842de2d03a1899f76d89865ac2a12b093f6b6d314ce390a9b0c51bfa9c7e292219e63cc711610b377421a358d9b26b869a32256cd1865ed73ae7c773f605890dfd86096a95dd71742cada1879da32bd3152d46aa861ef4a71c1bf2581c6fd2d03cc6f173fc72cff80b0d7dd8e0bffa7032a34e9da14991c2f563cec5e766861efc7f5e85bf0cede543df2c0e3ee64232b4197f0e951b1bce9e3c862606cf3b93b269c8be8aa17560213ecc181286d63d76792a09c9424660e852b4c3aebcd12f7429495b876a6fb6895e68534a8ee54da1916ad785f6a232ca7b588998a25c68cc349557b098541cb6852e834832edcf0b39635ae837c4efdcb3f9915b9a854e7b7c2df36ae3980f0bed46648390e921c9e22bf4b94f5fc343d10a7dc631c404ade89e64af4257de8d33675648ee132af4e220f959d0065b294ea15db56425c95229f46b793d438fd98e411a853683bf9ce2388342f39343d47ad5b169ee094d7a2e0fea9ed173684ee8c2a9e6a874bd09adc588a771c2c387d399d0c99bc857ce7d315e5d421fa366fc95a1435d5395d0777c24daaaa924b4129fa2f59e67cfca21a1772c71f2a923e68bed08bd84135735ad8cd0869858b4905166fcb3083d6a14f3a470392a894468265b9e0fb9cc81867d08cd49528b214b27ff09854045c72bd2132c41e85385f9941971924309103a7f14a29784fc8362caf78933a3b0850f9a7c904365ca8f2d6edb83b6f4332f1ea778d086489ddce4603ffedc411fad2169c610e64fa33a683cc85f548bd9283e2a07cda47a92164fb7c0419f632aacca477cef788b1b749a23c59ebc3851f2dbc2067d4a9919966557d41fb5e8a16f2cb1b48e75cf68d15774982b8a9986d8e82c9a3c3925cb8b5316cd67c5f1903ffec6ecc6a2b9d8680c332addd0102cdaf1d070d16c7945a37b59216a85ae68834ad6d4973c71e3de8adee25e94b2866592b3a24bed712efb649595761548ec8f57495a51451fbdf17cc61ea5a1c452d1b669650921e555ff0b156df89c71589dccff949ca2774fcf3e0e3d486bca149d07536f18e2a77c9a2d459f2304bfbc1d1df385a468634a19b9a8e5565e3f8a5e7d3b2ac3208aa2539387a19ee43ca75828fa8c7bbaf1977be1018a4e826438a3a7394c353ed1ef34b43cf259ea31e589d63322bb3bf63583db891ee30ce9bed93cdb839c6882a49768fa83f1dc6ea2ab2e8b1957eb5aee144db415c28694f2cc99483c7a829868a25a6494bc217527f912bd3cd269733f13b1de127d6ec8e6293956892e6e8e6ca2aed5d01c946892374cfa59a911369d447b9acb53a84e128d4ca3b0ab55d6dea1481ca65651a53248746255a119eec13d858fe8e225c78ef3c3ca2bd3117dce58f344c78550f236a299d0911be69aaaae31a20dba0fa7a15f8b6824e6ac67354d116d9caffc72a2aa9ddd44b4e32ebec144cbb38c8826365c2ca6ae58640ed1b7ba054db9c6ff2965883d3c4aade22916a2e9503185107de4642134677785300fa2cde0d3f02a4355a4a820dab896c2a5b428104dce30ae86dcac267b01a2f5071fb4f16cfed0e5488a1317e2391af343730ee4e286870ea7217de81faac80789d39973980f7d66958d6f7c1abe617b389c7e4e21038beba1d55cf9f02776575e9587aea13c508d241efac7eeefd6f9d79fda3b341222412643349c4e7668f5d27a354e4ee88f7568d41bfff707df8f5fd1a13dcbe8193d27c544630e7d6a903a462e4e475972e8f5636c8b1e322a8b2f0e6d869cb099a3b741dcc0a18f9807b11cc7af56f00d4daa14afb9b22d69446e6845b52a5ea33bd449bd21006de8b256489f94d2f3f5168500b0a1effc986415412c4eb3862e83e01d354e7838ce8c206f87fe81789f47920cc6e73a74a12beac48971533a8a0e6d568dfe9f2d5812499943e3e11fab727ce4d843e4d0ab68ebffc7d37c968c432779a91eba7c67f385436f41b3e9594a6f685f3f439f881d1fc7e486ae4cdb3bc3dc86def5655a1f57ee87980d3bcaea9e35f45ad9a38de14b840c574363d528945e76d3d0436fcd791f5987103568687522ae4fde0c8f816768d6db1d26f70811276e862e455586af61bb1aab65e8413cb2ee910ab30c2243f35932632652a4451f43173dc44619e53f5e85c5d03fcef961f3b4bb832f0c3dcede3fbe2176082a83a1c92093dd93e867c8e30b3dc8aa93175ac79ae3c6771251005de8321f85594a898f2524822d8c8200b8d077a45839c7901b52dccc04b085f6a344de90cfd14227d9a33b33e452cd4b161a710939c429077a591b26002cb4f9ba71b8cec61f257385662c2442773febc88b060158c1aa18e42b3447f89855a11431a654546663eda8503cf62e491935f69ea7f0895c9491b8259659299017d753c8339a2838953dff57544bc6120a651c2372c33d66ff8462cac03a23cf09bda523cd8c8c4a2608a009bd54a360e9ae2122250026541d9fdf1a5eaa1d670967ac1123431cc9f4a98461f846742a034375803417082009ede5d8b1fdffa1537924343199ae04990f319a2482008ed07a945897e84c35040118a1d74851f14b31698e3110045084662ba74c1ec7a417727c1000117ad8539e53267f087d7a6811b5e887ad4121b49e83244be1435e574579104010ba8c7d751a7b4ecad4c6e00d0a3e50566f1000109ae85c296feb07a920801ff471398478297e5010800f7aef8c9a1bb1ec82007ad05b9ec8f1e15846cf1516000f4c25399ae738612e5b10c00e1a0719edc87238778975d087c951f54da61cb4256116727c71d0e30e7296d338e3b884057083b63bae925ee3c6552d55110460835622ee766ab1166d0c89526a118df14669d17810ddd6fcffd92a3e66d14399378dd10fc99349010810e07f052d10c30b59f4f8a43b7f6587a09f7906582cdaec2cf7e94f0dc388b0e831dc4d629553c6ef06e1c52b8cce065b0ec6b2cb22bd62f3ffaa75a5acec8ac67b35b664aaec9f4a2b9a90254ac8ab6156f43055ceb8dc234f625d05dbd132b134265c78a18a3e46caa951f87c3c9a118217a9684b5336980fe6d98df281519d78818af6247bca39c4c4245eb9c28b533439464bd9c53536335378618ac662aa6c042a700400195e94a2cbd0fa3346f85513408004c8e08c6005af8108d8b94035b0823741795382920213907100c8c20b52b4a3b2da99611f47596ef06214fdabfce4bc709a49aa17a26872bee885128f7937065d8002b040072f42d1eac36fd1e4a963ccf402146dea98f155d66e7ea14ff4215c349c2a4b95c7f5441f53e4093b318996981fa84e741ac9a7d362e304d79682ac964679041fa8da4df4919b935ec453045b32201f304341060a50009385179ae81b6a78c70dfd235c26822d05d08b4cb4914534062fed3832bfc0441763eefe0c834a5be97589e627e843978896c53f23bcb0441791b36c437ff06fa14a9492bcc295f57f3e112f28d175380e29755749caae400a2f26d18fa54c8d0ed7d7a03759bc9044975c2eba7965fa831791e8cd813d780189c3c6286696e747b4272251b7d4252cae8ee8c483cca8a4e30dc9042690c10a5ad0882e545c8fd93da77497e5032d781664125e3082bfa0163fdafce3b7884cbb377a5fe5caf6f042117d37b418d3332bfafa44b40f723d7262c590a888f002117dc6cda09e521cc18b43f4d93b3e4644b8decfbbe05d5081340abc304497bd77fd31300d934108072f0ad1676cd08b199385dcab092f08d14f0e3942a724837f80135e0ca27f8c41a444d1d8186604d146ebfc1349dd22791e883ec4d9dc38338c19a718107d8ad9a1e768f8c133fc437331a92d777898c5ca0f7d652509172b5a5cb082165c60032e78d18756b733fa820fb987d3830b5ee4010fb581177770fb736f5f8002f80a3050b78a1776d833b2e614d9a93cbf827f41f316295ed4e19827c348615bc3e274d0cbd5d21fb4aec77c0e85241242438eeca7be90437a713a4279ca97ce0426f0c22d70410450b0800abc88430f7562b53898ea901c0e6d3b0c0d7973788994bea173a0f966a973d8488d1bda98a12f8cc7ebe7e55eb4a1c98b9c98bb5286e8d00b365895691ef125d19e650de234e40fa99d2a075eaca147d13535344a08b1c2176ae8b22297a5f893c36f9f863ea7896cc8c1a3ba945b7881867ee432cfdc9349709033b4aded69514d6386d65ac6a28547e51b7e199ad78a551136a3e4284e863e7bce96d02ea2eb2e62e1c518fa2817b55f73a7f5a75e88a19dd4be1aa2a1b355270ccd47868e34af9ac859c0d045b9e8554b1633ce78f1852ee25f94d98cfdf1910c2fbcd087b0cc295e332cc478179a8e9faca77371a14dc957e9595115757bb185a69259c40d31656121165c59185e68a10f2172c4c73f29436fb2d054af874879debcb863a19df81e3bac34889fc9021480062faed07af889112722ff94d6821756e8f244c8679e8fafa00b50005fc1b3043af00116bca8429f2148f29e7330155a8b1f29fd09c1ad2322d84a410b50e0801753e8738a7e72eea1d12662c0bc904253ed2177c8a2d221df1751782b8b8956c6b2922dc0023496f0020a7e9271949b65377c022b9952e47811fa9e13f4cf9de2af8ef77d5a1ebc6802be608215005f2ce1f830e3f43cae793a25507933e38821fa7a4d2a2dbc48429b3d8e82684ad6d2233ae00512bad0d981a5a08d2258ce119a6e146172b538eeef18a1711cf2f195e5bcca7c115a1f0dadd25973c7f989d0e5bc12b9722c3a67cc10deea4c0dddc9170d085e08a1d1fc9124664f49245e82d0868ca715733c53f70910ba4c21c78c611a98c47fd04c726fe0193de2317cd069d4369f5207929af7a08f7290de604336fd79d05f8776f09508e7baeea0f73469ac66ee3184a983d6e7311c49e19fb9e6a0e9d8a92da2486fc6210efa5021c38b9af2556adc0d5a738b16b2725ed8a003c72315e98b412da95c14c10819a52442af1ba2ccc2f2211c1742e83aefa328baf2f0f4c7175810b808021740e8818410ea16278ba9fc833e68789037637be3f73e68b2667b7fbc637c0eed4163595d3e233c681de67e716439e939b4831e7fe6a00e72c22a223ae8fc1f4f893448a95225077db0ca9c1ce928e13a3868524a68387e6a2125fd067d230ba29171c7850d9a8cbba1697a9c4e886ad144b8c5f58b9c3b3492166da485a0ab39c54cd12cba6e20939232beca0c94459b9127c4a7e610b632b168633ee34fcfb08a8c8245df5a5182e536ee4ef3154d683f4c111df67e5c5cd1f9b8f86eca1eabddb5a2cb31b89ca6ee99d02e2b3a0f25b3d1718239ec57d189548b449bae8a1e9679bce66b53d1c5ddd988ba1df11aa86853caf27992cf3288e6299a38552ab37153347da1f35ef1bf3b4ea5681c8be5e4a9d318849e14ed9f79eaec3b19453b2a151df349249f90287a601172c3bf4a61cc118af6ad3b9c88ea4fe81c287af0913c65e74ff4d6efb9215ad60b21c4138de5ad488f90184be39de85c3d780821c4c5fc83134d499f74cea94d34a79f3dc710323b21d5441f763c895af846c828136d8cf951e6e82f267af3209372eb76896682e78b18af32498a30113725a9124d0e1d52ca30a41c9d90124d4c07176f129a442b9e4386cb8c24dabf6ce12375ccc92d25123db858e19366e7490c89369ae6e33857b54e9a47f4a92791376e46083f3ba20f91fb1abb1d3fb478235a0f79232b55cef6d230a28b1b91fb4c5652fc2ca22f09df71a2c4aca93145f41ede1523e88598e825a279cb7810c91be5184388e8b1c55ebd1caa734bf810edfe684e5e0c165763883e63eff75cd179bc55219a8d216b8964fd0cdd428856c463acca9c83bf3c883644f335478e1dd59420da4a19458d31ab6b2507a2d19d9e8cf462c7cf2840741e9a514453428e2effa10b33b1cedd28dd38e5873efa74849042b333c7fad0a6141aaf328aefc8333e7439775ae71e6fc912df43a37f792dbe2447dad4439bb14c74c61062ddc379e82c344caad141d47f141e9a88d943f4412aed68dda1edd37c49228fa79c658736a8c79aa68798fa31ead0475506e153e65392930e9d690a3d0ef23887f65f3f6c989ceb930e39349b1ae68168461cda8fe3197a3e929cf387439b62342ffeae4a0abfa1cb8e672eb6916ecad80d6d87aa6530f19ce42c6de8e364fe95e4316ff09e0dfd7f0817f2641c1ebcb78676ae420ee929357732d4d068b5c79332c7395c340d5dfc0e9e19f31c87dcd1d088bb9ab4464aeef93f433f9b3d787ef64dc7a319fa30bd9347d72f388696a1f398eb31c1a4bf572243ebe162d2f7bf4acbf818da8c8d34c54b2962682be49c817bbecec647189a4995b353bc08863e63c43055cdf3cfc92f3461fd73d44c122fb4213b2efa4746179ad020f89c7c0e49c2c9857eaefa5d2cc30a916c0b6dc8127363a94c0b4d68cb94a311fe17bc2c746e162242555bea14c1425f1edb8357675ca1ff90a81e72f058a1d51f4d92be39438b8f2af4214163caeaa7a77143852e83dc22392bb7418a4fa1cbaf99e69d53293431c3ee90518617ad5914daf5fc29a3cb50e8b378aba49b674e8a3ca19fc87a1ad95d62734c045b51188013fac89afe3197e590f135a1c771529e09ed37cc0ea7716c90d45f42bb9633eae988081e2e25f430e67778174b999493843ec376e933d94cf23148e8a57bd7a16c6cee851ca1ed8c3316654dff1d67842e3fca9974ce1f895c8ad0eb83dca01c7e86f87922341be61f0407b137b61b4217de11fa326728a6a5109af9141556821c843ea6486cf02c19f9fb40e8d2d7d73b6bf4079d37feb238417a32321f346995fba83d85ce690fda14396c748ca69c7196078da869dc080f3c553877d055598e5fa17374d0756f740c2679eea7680ebacce42fd59fb91ce6e0a0f58e8faf5937860cf21b9c217f4c5f42ca006cd057bc6c8ea1cab568559286a5ec2d2d5ab3c69132aafcf27c66d1c69f1491837ab604872cdad78f8d93cf63a3a4c6a2374fb9d9dd8fe2270e8bd6424a578821c4f490f28ade4198f611cb0ed263aee8223a8c39e60f392d525ad1768a0ccc333caed0162b9ae8d37e155d6390f4fa53aae8a7753d64734e45df30ce231deb64c1345474593de6df48de50937b8ac632be1442f08a299a721c191e4b8666922f45d7317b8a1c723b7f3b48d17c66cafbee1933166f149d03cf9a51589c69df44d1a514bf1b36cd628c53283a8dea2037bf7a68c780a2adfc1f2fb6e4299df8273acbfa8f634e33debe3cd1e5c9a83e97fb3f1aa7134dfe8c49bf62f88fcbe144ef48e6f3a7946ea29d8c33248bf9728c9b69a28fce2886661c3f99954cf49aa3668aeef9b935c5441b216c7a78b72ed14567e7c8d2184b745126c7a41d231c6f5a897e5b525c6d901be3a69468ce73d4af709e1bab3389262e58079d183733d624d1669e6821bc5fb8ea8c48f4e292819af90312cdab6793890dfd63668f68e22a672696c6114dfecc949672a6118deb65c661ed9ff960443b9617394a5ef5fd16d15be4fee5dca422badc334dd21f5295e24474dd2186bc55952d328c88d6c1e68c2f557b88a6b3f1bec6f74a8d7743b47a3a1fa288669c520bd1564829e479fca8c32d42b451331cff55f79b9c8368f2a8e3ceef7b29d11244b3f94942786604a28d77bc61da16cf2b02883e88be23891c4e8305ffd0bcc6caace3fcd05fc6e76b6d621ffad0ec31c8c71cafccc387364bffc78410c2e61cf7d0a9c72491f35e0fbdfc9a68e8966439b379687db24f4b7ac620d1c143334935f5c3f4f0bac13b349f0e274b95cb6a19a9b583e8dcc828a2c9269961d0f7d2f8a312ba4844bf7a1a5d519e1e2588e8bf7be63c8e26f09202088c6f401787e8253354c8493454340e115d18a293d89d1a49a253632a449be9c0e3bd2382e882104db008dd92f906d1ae07f91e6f1452f376852e04d1bf8e6af7484eb75a02d16f8a9443638a03a2919d5839aa577752c22efed085698f255a9a1fba904c26488e29c42efad09f465407f91c3eb41e3eb9996ede98dadb432b53e153f69e8a6b165aa10b3df463e6fee9b06284ed4330828382149497441779e8b26bac637fd4090a07a826d0051efa2099a3abe2458e29b9ebe20e7df0d78e513374286fefbab0439bf95bc9933c9c0ae295a18b3a349f752fe722c70d5de9431774e8439e6f92289752cc1b1fba98431772c83b25a2b1498684b46c39e8220e5d9c6b309e277a8aed403c7401875e1b9f38ec8bd16bf10e5dbca18fa199f253fe324b961dba7043933be42f4971e81f5355872edad088ee6c65cee7fe1d3634153de4e5d421ccc235f419bbc2f3b7e5154b0d6d8629e48c3957529c4e1a9a559f0a792966cce5a3a15191768df8bdd69b33f4287ca3ecb9b01eb3c40c3d0c9eb3562ccd19f932b4b964757e839856cc20438f3ba520d92e92a2c3636836acab72c21543fba922bac38e86a1f3f81b3a316e2e130543531ab63a9ee70b9d749aa954c86fd1f742f31353d685364455ca3155d7e4422e349125632765b685a6417955f6bff05995167a6c21f10ebac8429fc26590bc8dc475dd3974818563c89e41e6f7d5eae20a8da68877a02947c81d267461854eaea2e2837c56a199fc3719dddeb6dea8d085f4d68c634ac9c263a6d0e7376818246643e78e14daac11953334f158311285b62b9e9883568bf71c0a6d0e4155e292e3096df2eff9b8972ee16427b459b55276c7acae689bd086945f3ea5993ece6b174ce8416358799aa34b686582c34f35ab0b2518b12693a29a23098de99f9585f3ee6c31116c0dba4082e2a79bded3a39b2f47b02abbe58a1961cd95db3a3a772749045b2e60c107ce0b5d14a1ddec31f31c4cf0065d10a1f3938c2956c434e86208bd69f438aa3cb99d1c42e83c3b861216d5372a82411741e83f6eea74f2a80c930584fe533408ae979e3246bbf841ab250d27aaacfcc4e583d624a70665d98c41173d38fafd522f28822d4642173ca05386262167e59cfc10bad841133363a554897c31870005e8a03d5fb7941c37fce3a4052c48010544d082116c80055de4a0ddf82e9d8267cd9b5d1c343bb3adf11fc75c3e3728f47abebb1fa60b1bf4fb41437825c91ed36bd145ea18d23be7a4a132e582167d3689251e634e2eebcca2ad081f77ab8286fd03de0213bcc9222e64d15e1691e4e9172ebab1a802c0a213f1d9941e62d24979045cbca275d1ce352bab50b941b87045a3123f06cd8fa1638b6945ebadc182668ffe215758d1794a67c879b3abe85b538c41443f935e45156ddede7c31261c64bca7a217cd9d62563f50d1e7f8eab03de34adef129fa141737f4c3d41da4550d70618a26eb666456a570518ade43498c8f3a04ebfce18214ed75f45845cf113aca32f015acc0f3045c8c02b9108572110ae4021455005d2e3ea1838eb953e26211acf9e22ab8800cfe018c79092e3cd1673031cfcb8430a26e27fa94fead21eae8b85938d1540e96926e996748392e3691052e3461f0bc992506cfb14cb4bf7ee5a9719a37f660a287f1c28154e8ec70fc257a141ac7dc2198264f164b342986389eba65ab3cbb12e8c00748c04525dadc11729c980daf5d4a34152ac557999cc93c9f44fb0e35558a19368e6848a24bcb1d427b78fc9647a2cf0a8f53d0df98543124daeb97acfe1f7dc4db171a73e629b970441f5cd671e675e32c9a3f70d1884e5e827a8a8db3354e6144bf9ede61b52c67941f8b687bfd3464dd8ad3f3ac3e507b7ce04211bd5ed239c97f94f3337c082e12d156fbb57778e8558f83820b4474197d4adfcddb53ae021400eb0345c6c521bad23c21b5c7ac21e7e4097c054e0114a460043318c10946d002154ca00005f88026b830448f45726810553f2ea60ad17b72dca95274f7c00521ba96bc10dd3b6507d1658f7a1252bb68c31441b42fc1439e73e412b90d4497993d21c6c999717280684df79206c759c662ca005cfca1b3f889ac21ca51dc1c3ff4d79d25f127c44989f4a155911c99cea69127ce87ae1cba5476871d55dc3d742165ece39ef3d812d543b3218a87df18eef2e6a18b7ed987fd7933b2e2020f5df46f13a914d7b2f959e0e20e9de338f963e6d64ad5d8a11f4d294ef020a23daa15b8a843fb302a72636673e0820e5d85649798d9433c0f99c0049e8005e8818b39f432bf39538a5bca8eda19b89043e31a3e7314c9cd0a713f70118726c5acbadf6072c3b94ce0020eadfe064d7ee91733242c8881b18b03176f683a857666947fcb3482810214e00a5cb8a16f07f9e7416b65d49dae818b36b4924dfcba5747aa418c0d5cb0a199a0297214efdc4a4faea1c9151e579cc851432f997b19a63a5a5609388071c069818b3434526ee618580e9fa25482920208a0a1b914eba8baf16124242c707186b63566338adf0ced6492b5d0b82a031765e871e8460e894ab9d08e810b32f438568eba951653cef931b49631f665cf8c2524590c6d909093571975a36086a18d934a3fc3ac21b5886068542c87b0a41a527fbed05e4a9ec9b18c0b2f14a707b8e842ff2f1f3f3357e8f20e17aa00cac516aa00a7855e92846472b971c8052eb2d09b24cf39a4a2c45ccc0803175868c37fa86cacec3ef15ca18bf1dc2a936c6ed9d80aad8ca7783c1bad42dff02acf635589b21f15fa0f2fff383b0c3ac19a421f297986ac5f7444090326052ea4d06f87caef74ced5fd89429743f9887b83a1d085acf0a70e33e74b9527b43166235c8e2924f775425f19840d1e37725d73885c34a1c9792f348639c6085c30a1d3d41a2ac4867e86e2127a1c6364678811442e94d079c528a1fde32209edf9a348d11ba3be8a20a1d129cd31db272332e3089dac7cbc181afec57d10810b23f4088891a8f4ee4402b240241088c3a160300c068db31fb31408001824268b0663a12007b4c10f1480033e1a1a362628162a20141414161014160c088442c1701808068302826020100a0983c4f02858f8006e46c45e7c8595611e80f12b06c584b131749360fa200db4e50ef80967c1e9f17799a7984f3f49c546f61ccd8d0b435a58cb82c69a6e226252af826c9fc08905a2303efa85c4ee29fbc680b72b10b5077e5192d40c8452aab118ce40b481a02ed5d331c293563d67aeca8d920c09068da55142f5cedac8a5eab088cb48507ea827c7e6544a9bea023c7f892a04cce976608d588fd98724796131c5e171dee344e53e0dde0ca4660489457b6b9d67bada961f2b910d0211bd03159f02df3fd9dcaf5a614b0f0def0ecd01a810407766e2b218b6bc65c8071001e1d640c3c93c5d609d5fbe97bb7851eeacfb74ca570512c03f65f07ec9177e1bdde66c087f62eba911226e20240dad162ef765304ebae279aea8eb1597d0d72475814aec822f57012ca443f8704d01b9eabcf5da6580e69287c18bf139725b84310e6dcb6b35b697129850d2827d548722f904e880d9854f45c12887c61007c6d40417383e043efa36fad4363823183d4a8787961e5bb5d5acd0db3eb3b46989e366c6a8b87ecc8aa2d6839a353554394d4aaed28c0a05ca88e3c562205a5a412c33771b5da0680559b74a07f3d7eb8d5ae1246cf3f9f98fb828981b5a111ce9873bca6c941143851d9e384f0795a7aeea2e825b457539f9210b8ae1dbfd134a0b3f41bbd017fcaa886e8a3e2e8cf2a7cf05e478630db7cb184219a4ea23df306a11c4c94d0fc0397f32223a63abbdeb2e81d0a966a78319653ba2d4e7349b03e51c441c4cadd49302124624d014cccd0825835b013b70b0026e378820a577c6f8c51242447ad1a19573feb953852d44845d6905982519d8205d6b7fbdcdb96bcca4422826010cf5fea2551169e760086feaeb07f24f30fed84afe4058041f14c0518c6b2b1b34c8cb8ad2502184f63f03c6976f79dbf811144e47eeac102866348356b5a0c431583b7a531c814943d56aa700f2ad1dde01b67cd6491284954fdccb8b8eca7f92360c14b82a1c3ca0d94a273fc6868587d1ca3723183c3313e1cf0d30f58fabf38e4c034de97aaf652231669fa760506d4800e9a5376371153ed9e10f6ced99a9d2c7a4762df8ff25d8ae6c31aedd06bdb6293f41d802ab0128efcea22354e58a8802e11a54fc6fea5fb95ea646b6b629b0879a6bf7d340249e480c404edb0d238607c69b217de9f87ca3ceb259bfbdbf7cfe3fb8e4c1ab85f7245387eb1eccf1e7e0953ab7ebd71638b69b6a3e0dbf9d90b1351d60c809f4b522d7835befb5139e04c7a3e435501c7762f4c52c523a3151e0b8d3fb3974b4cd35413d60be69fcd1b64c351e6ff3007750c8208e88db45541272825f561cd0747d462f5338b3eff447ae0a2d3c0c0907c734fe7d2c6cbffb7b128efdd72ea729897d3c832c5901922671d77a90c2de4e11e01dc1395e4aac381bde312b5d6dcb99c460765da04b4aa6889f7665c16277e7e78690ee08e3e0e40bc837ba96403863e1dee0cf7ca899ae9cb77e871e078d700ed8e4ae2ad047ee22c01941448cf370e640568164ac81e81af1fd8f07d6014831ad9d5e6637a1b0765fcb1406e25244e5b8d48824a7b86aa57e49c5d75469cf6092e320398e2b4738e6a03cb6dc3203559b864defcc146e43d00785459435200d3d8000170405cb8fb9d43ed59a48370c15436880b61f83c576929ec4f338b84ddc524c040070c52b13b9d24192714800932d1ac08de6087af871ab6f0486b25b6cf2e3b5c9041ef096f7cdf15fd5c5b185088cf792b5ce9eefbcedc1244008d1c534b030c88762998d4c4cc1b5b886820a67cefc0baed7b25afc37154cdcc90dd0cb0b3025ac737543fc1048cde4b9a20fad0a29402ed31fd40d026a04f318813e72324a7d9324d325c8a8455adb2f90fbe67b43dff0029cab32162b487e257e555ca4bc683ed455ef1aa08aabe82dd67193cbc4ae4894e96676120b68139ce6718a5b167036ed23c90d89733124754ac8b95c2d90af0c881a83bcc8c2e0ce70e4f8d338f769a19b7dbb93850daf236c02bf1189b9f0ae066a91ddd136006121a50f2313284d51a5607ebdec64d00a8cae210cb3c21eaf40f45ad26caf20844e0990a96db5128df48744a74e74cdaaa862c516e303b6b343048046adaa1f88e5ae039ade3660302eaabb0296d394fbebf0fa37ef3d3a88b26794b8c75980281887d2e07c75ba6118032b454e3959a4cf9678fdf9b406474296fcccd32e291638a08a5cfa33b2e8a211568ee5fe5c18c1f18c4102110d332ec40eb5df374ab6fa60405e9ef928b00a99898eb42c59db25636ef6568ef209a515ac89392f759b604a5ce7c90a59cdc36f66e5cb3327631b2e52788567f292506a096bea8a58663c312954b19a2b787ec21d5ca533aea07a5b739ca1fb6bf1dda747fa094cc4087a6d32d48244229b966ddacbb0b461d9332e7e4fd6b6aaf770809b6fcf8cff335a1b6e26ef784e5b6b4130b136a10dd5a6c78dcd7066f78a06e78a34f34f6224f2b942030edf760e86ca553cc2d62b54c270bf4cc891487dfc9425d24baa3f28ef130454ea0b9fcbc2646b3ef59f6723ac0cb69f47a5ed7b3d1d3a8f3be2787f9364517616e372daec1464612dfc2c468c06526edb747018999461916d09acb9d2b5d20b6bbe612f5203887f49e9b1c978fc04beb8192df45e2076a91298f8d438a55ed5b52a31862d13ecf9419fd68c048b3577285c800747b1fb70f1a4b3ccbb6225560e17e9d9aa2b9b25a6a0bf46a9a4c64d502bd2ef24319d53b7283b8055eae090db8d93f84bfd8d1f1d5f3a81391f709d9610576e0b2ad167ab3ed0c49e942ab4a2ab61d95365961096d681c37a8255114a09e29c0f24568f56ec23863dc4f03a40a130289142c5416844f47149b9501fd438d485420f149092d252209e7250184521290e2a134a8382a058280c14024aec28609c85d31c304a07ec05aade4c144d6a0a513a62300c0f0315a13a52d43df1dcc5090eea80da50bb58940e711e91ba42e54101fd2a8a8337afc37182ba16c5fcce040a88d97f361a0fd5370cc8f572a8f1a8beebd3f3329009cc2cd4f0a280867c13b6c5102584fb3064a98994f5a8578a06c540c1a1b8a3a2524828118a0ee53c0a958a42598d724ae150001411ca874a430150361407148582a1aa505e2806058782298af9dd17af3828a86685b2d8d6eb8b26a1c489c2a42828209407558262a11c280c280c454005a1fc50b887ea63b80f1c2ba8060dd507323deb0354ee45017f8626cc71a0741823967e87d2a08a07a5c32171ce504115504016054c044a26e7a0ee3d5f82d538a13250009494a1fafaf57e407250ee88027efd90a79714b004467dd514580a4a040007bf256bed36a816ea0d3541c55008280c0a81224355508408d577c0c5580328461e457bf37a472f28c35196140f4aa4a2ee715cd2a974a80dd4196a110af83090e4543427e0c90144b9c92854e9d561fd02c5a094a04828010a0005433150290f05b49c35585dcfca227e14d036bca7b166a038545a096f01d52cc5fc2e194a04243b8c770215400da17828311400854321a050281a2ad541dd035ed262f05009d4188a975040b6cb0f1417541cf1fde08a1b0a856aa170a814285305d527e260c80dcadca080cc8e6b9d80d28111e9d24f8e2a985262a474408de38048a830d43ca21485f3a6a11d8a84d2a070282c14e450ccef06b6726201e5818a360a1832573e7636503a90959a7a8152a0121b258293255a2605d40935801aa122282e14028541b1a13a28d245f59d6407fc1a8a2184ea4301fe1a1f5402a2fa54bd97fa59150742dd782811d0261b7813ea54d42e8586d24279283a140d450d45a0b28d022e404c4501ac39b48202f24528c41150f158373127187ade58cb2c440c254b8fccefd18be1bb791e301dab06c8364a270bb406c8235cfecab3a43286acff4b0fb08eeb87506e40f210fe30f45d79002e291c91c4000e03a373c4999671ee146b34f7d6cc536bdaa40c21244d5616589295440a150691720d2bd99c1482f74d8d8649569e29f210a70440df052c810ca134d69c1a6c510ee3d0f2cf55bf5af9ced6170351c0c9f49f16224bcd25e548e2827cb247f3a14f95e4f748dae0ac7eb9378422edf8ece4a50152433393d744ea92ec4c2de3dc2904f2ce552843693d011824d32b2b2432c9829564a530057401d7cbb2d6cf692768f599ee84b3de535c6332c09dc85a879a4a514dedee97aef6e95044c4f307bf175d5fb0629b91a92a737f221b44a64ca5fa37529bc9e70edd3c722f6f316c08f81ee95a299ce2b661a1c5e1feefd489765d9aad67dace086d92c5fb5a822a8076118a60a84a486ce89e453c079da505ee5fa047f133243e700c182eb5effa9a308775529ff6b24061e3121eacbfd3522017a610cebf043f0a2f18e7ad60b216490b22a95c480fe55e7d483a68575433a91fc006468d5f0e2cc8fb39706300d961cb4a4a33e479bcc1ecd40f5366b424bd0b70577eba4481d5d4e0157ec9aadda368e82140ffd1b95431a003903f6fe329f7d7c18cadcd17e21ba715e2bf79653005771b68c506c7c393ec438b8cf03244b020db82d22196df1b2d4311e6556b6512d1c0965555dfc8767ac0e97b1fe3ec40fecb31e0a43639160191b98f7da0dae06326df0a16ad4377209aebee2c8f4eb26c3e89731d59af75a5f85a9c82310c10429992f62998b7fd5de8b6ef68f3a6f847cf9945bd97ec285d5c573e48ad88ca573322e1eb3360f3ccf188e5a290b4f4ad9fe088d49b974ca2358cbec20c2707a9cf55b80a77d61538c90c9c4f8f5668310bbd7bb511974600120a8d2b0f3c975c1f417b8fccf8280f2e39ed117b8752f3691958c1e3210a0c206ed990a6c671bbf665f9229b6727ea76539641ff36789cf131b2fe364119e5c80701761cc2319a7fc461a77bac8d780781236534a374662726e94012cdd3272990ce6bdcdd04c0bcb8313c130d8194d60d445734b6ee1b835fa4c56396604912a4bd771c89f52d854c0fc49f5f33c0d5fc22cd8dc8b7471a75e419e93f025a2e4572e1916ab2e8a45bfa15ab7373536051bb5ae84aa185023186ae8bec41e80bbd565a77742ef4566d1bf2b4d0ee725539d6fa61ae731d4833a23f1ab3d684585ea4631098d3582b50789d1bbaec7cccbfaf06ab5078e8a7d20f293cf3a3622d633fac533c6dde632ba82e6e1fa0c1978a0cc5e8eb3277f566005eb90102968c64acc19ad2015f2dc00050b3c01429510a8b8f1d80172b1208f9f2a4d6d41b61ed293b0db5968dfb182b2e13f98de895cf90d080ca3a7a9a3803d9e1205f048811885cef0ecdf94ef678e22cc55b9e695dbe3a2261601e706ca9753dba1eccdecdf24d34256732c9928a48cd550c0e17fa38446bd6462f31631c3a2e712fa386dcd55a5607fd2252e0a29e0be055d3bb3a45dbcd961a5e04c0dddb9bc466437fa119095db04f40ab51391b691e29ebb13a437e8e9d7fb57b31ac6a8dde832384f56dbfa187f53a2aafd679652acbad0d9017b90cf4cf381f46068b17c80ad87b1bdeb4b254acda337f4fa5bbea2e27df8abc2f1479fdbf255773b3d6c78a6426b39b38f87c020f9f7da074ca6646023891a7327755400b37db0e463c432253536572072964babc600205bee47f5d906114ab642b3c2411719381a8a5f9f97fae5ca4a13e8958457400d495292b0906a33f390a7ecc3d574a339842c1d7d84485fb8bf2d22302300b4b8d8c755143928266878f94de5a3708370adb69a4d97e660c20d2bba533a01ce963e1178761802905e0f502592f380999fc22e096b5def281653d22c4c9b44f04984fc385793dde3f36915a9b526e2ea1d506371d61c29c1e01ed146b1b15fbb248a6c44be240d0cce1115327ece8cee03b4524f1cf62aa9d95341c817c1568ba2f7a82100619d633a82909543899045b18600d9285811be68f33fcf7e4552a04c198a85eb145e361d99470a56936e2bb96539f7c0be614b6a81c7d861af0a6802283a1fecf8e57db5d304ec151b595d95c3da38d48cdb29ae1f944ddab4a6d9880bbd72b1842d56ac26bfbb29e6ff881e1ed0c9e6e61304dcf84306b76fbd291aac7ed99b64d6d9c430e4b67d8f5e52b3f253d8e9eac57db23ccbd83471785c494f5980d2c094aff481d4d909735204ab8090836c184fc2178ccf5704812d38c18521219924c1691efb3765ade214759d11cc680c2b84f2b9603b657e4d3d84e715ffb982b518156210ed79f78b6e4d166110d66ac0a32c1eaa6aaf365c93ae103b0f465d6499084f86a0d0b86ea5080ff9b2249203259af40d4a0cf84559252ee0251123393961cab5269904d88adbe18da72bb050e9d8ae0815933ced2ae52fe79b6b0659ebc478a945ee20d6ddf0a13a4343939b36b54366e281501354c40778b203b5cebd340d451cd1cb7032de304ce290254e0b7595092cb95a0bab292825e14a59c48352cb5679aa4b07651951dda606efb851afd483e5352d0129e40f5c101d111d58e9a3a647afd10d54651d0f79b0dc89af5133fedb1e2ee499b1407c709102cf8c7fcdfe3eac94ea44c15b991ef90ff1985857d4482b6b890ae64f92b7002b8e55bbb3708e880c4cc968ae54bd1629e0fe3b181143af0276637f1b2f51f11e07a856872b26b4994e233728fd0c896ba884c6da979b04517161185df790baf7f857ded591887eb64cf1065342689ac84d6d40573b8eec7e9b824a53bbde1492216cbadc1db42ff88d4c62a58e758534321bb12e7402237c6b54d3669b305e320471788c864f3df66c8155fe44942b40701140edb4209d3a2e024d4b7f364e024c5edf5ad7d49e809889fd1ead6da7f06c3180dfd6c5d40379d00c4e7653d47a8f1b3998788ea1b9e0b1124cc2db4d0350b070d616c1143ca9e9f965c5c60824c277b5afa243a068b226a1b6131af8c39160abd76c0dda2ab18699609ba487d357c13750631c73027acd5030d7139496e2dd18834da59a5aafac6e09b1af23679132152d373af232f8628e88fda6df5e6348895d851ca5a4bcebd417d184a54e78f38f04749bae54b475943891f696f5da7b59eb250a895bb4ad9992a28597e782215abac9bbcddd0bc99eb2f208669915af4ecb2422ab4719dee7a7bd594c7cd99e7f79d13371ae538529e796f7a74b97528dfdccbe3fe724a7ac52868f3e8f4e922182c938dab8a161aa3e16e3c7eec8a4b413a738ec8a0e6f02d65fef0c3c408e846a3480afd359e2337cd6f938d42d5ef55efa6f9c9a2f575327484b4fa2b8ada9753cf94010143135af37fa3ad398d962ca3a5e7f815c2ae2e5b660945102d53018ec62f03b75e944e13d7feb6466aab40e4ecb4e0d82d35297117bad252ed244fc9499eccab16e05ddaf13df64fb69d87db78e18a85879a116f5346e1d45990d16556d1c356bce4fe2c6fecf090d7fd5e14f7827ff09b75949c596492ca829257253ed312f07add55a7e27b8687a2040a91a86f6e78fc16b9e1da8be2208c0e9bb1c332fc6b21d9d029081ca3081461c6ad1f2998d27152e52540cf2162aa045d4fd4e30fda3005822a5e230597c7b621557dced1438d12643b7a59e7c45574b9cf3e8db7fa93945bbd477369f8020cf67e060055503bc8aae6dc719b89f7b868a4dfd3877793b7c530d814f41d89a8c8ee5d809dac870088564c5f581bb9a50a0aba88931135364bdc67ac0900600e35a4e99e69091d6041f40aadd669b5b07fc95d6d2428b4cbd7c50adf2a398d1f344e79f1cb39f79a3df5bae0a15a7167278f832123894b7a34289ddce8abe8c1ef23107af300601b2cd707fcff644c357d1eccb000af628ed655bbd43b1549fd01128a796c7f4a8aef258d252dc6134fa53df5e278d8c8098d5d859ee5ce9c4a677990790f61753c51805e35687a6f6e2ed6a4d34541f31aa5c4e67e14289a909e236fb4099efaea8a7ef128d883ca0b5a054ded235f429be3af69d3b7a3209ced2da1673f286bbeb0e69b6b1e151daa0dd583f2504828060abe83cab61995351516b0ec81baa1c445545fb024ab19ca4951a84653f80f0b5409f5873a50582828140505a1d28da87bb0fdacac40bd500a8abf45f5ddf01cc3018ac9a3744c43deea1d4a41a92fa87bf0a7060cf50b6541812a4a8711312d4841f150a308459bcf23d720a04e18b5499d5009a1fa78cc07fd375407a0802f98b26947d497e626c183357fa173053b83ba97b408110a350215426d040514f0a2eca427e96979a243c9507880d28106ffddda501ca8b9400155dc04f2a540dd4bd97d1a00144414ad14a4a8f54686074d38a81f5407b58102a11c282e145643a12a52b25e08d4f8a222a90705840a41a9a02814ca4201a9d569c182e2a0568a7b1f41cec6004a93229bf31965598f81ef1cea83929f02968f45e98a94d49850b4a530aa4817a8035584aaa1ac1e14f0edfde77e146d0b0f440e050a8028d1a7bc14e2282081761a0618281db233a47871eddb8262a02618d5d72f30e5a85054358a762e150dd4a14e501dea008529aa2fa7eb6b684145739408a036b0ee02f581b2a14828351406554309a03c28020a3614b05c2f7ae45204a09d68771ee3be3e7ba2a92423373db0070724083178720252ac0cc502fee144760c3750b2a4d001c33f3f3f3f3f3f3f3f2ff0bc6dc1362b0a5bd85226f2ea3665f3de94524a29a554068005462384b4c602c20680c3280007029f0c1e0c5e0c3974e0a8c11683183f2846f70c426a76184483069723860f8a7153b5c3439cae57174870e0c8f1014b70e0c80106aa22460f0ce36d5a638550cd6d0c1e7c3e27bb89f661a2bb48fc606237e6e4a1f2c0872e968f9b73874f596235b8389ff0918ba2499b11b22796e8b33f705192f74caaf012a14f076f715529ef51da47a794f8b04561999aa155a5596b99bd95f6673a96d0ee79c4472d4a92a6eb2f61539592c4e5f0418be2e75bf334a531c8963e66518eda316f50f2c6b8199445e98495e65d9b143226f98845416b50ab175edfb7a28e0f5894367827f956a4f9092d31f0f18af28fbf89cf3933b5665d513eb165af94f2d8a283ad286f28b983e7efd71bf5072bca277d492d257eae38ed6315e58df3796327ee4c92f95085d99af655f2ba23966177d2a4f4eb6bb055200408e02bc1472a4a6772c939b4866c13c30f54946fb53da9cbbcdec6a7284731399f464567fbfb618ae229a173ba7e75ca94e58d8f5214a48930515b9b55922c091fa428273159a9def07e8ca224f4cd97940d8dd1ce87280a6f3d424eecbdaf1285a23c2a359ef897e94deaa02809a7f526e7fe5c9bdbc727ccd9365713d93bb132bdcf356a1e4f4c2ba164c7f0e189626cd32499dc98573646c34727ca3173c97af1fb41a64982011b94a3c207278a1d3dff3b9bd69b5bb5f0b189620cdff145dcc5c91e6aa2fc9e6c935013a4783ef9c844617468cf13748e3c810f4c143396f6d0d6fbb693f9b84441ebc44b8dede77b630c3e2c51d09afe5a4b64eae6eba312a5df24df93b2519e8f8cec482fb09111005ce18312c51651da57a1793eb993285f8e94bf3549a298c93fc713dc4795d47f44a224b498feca093f2051feb8a369b4c66ece691d7c3ca214324b8969339c245e868665e2c311c5f8d8e8f1eb3332628a8f4694426a9824e660c2433482f0c188e2bae9df9dd12a4ffbe06311a5f1ff924ba78512ccc187224a3b6a2c6bfce492e3a9c1e685df0f3e12516afd64b2acfb40c49961937aa7f410a5cd135b74d64726e5a8147c18a2982493dd39c9a28f4214f4fe9fa8ca9f244611214a6a94ccb5657d0ca260f1a9df6265b5b3461025c9c3a70e9bc31f8128e9763a3549d43cf80044f1ba939af2e0c1ab3a1f7f2809c26487dedc96e7fa871f1236e98b6509267d28e66a499126e7689d4af8503031b5f43e7b7787d31ecaf9e22628fb204d68480f25f1bbe745bc6ecde7e4a134fa6173c8efa84d62f0507edd6b8fd9aec4ca983b944c4dce76e9de6dafb143398ecebae32a2a37df752867f79b4c22371d0a32a47649a194483df11c8a2f4aebc92d5ad55b9543e935549e096fc6a1a044b76d7ad072ae261cca5df5267f43399f1862abf7c483921b8ae19ae4bed126fabc6d28a8124767c7ae8c3bb221133f61a619aea1bce5a6dae39f546e721f6a28c63229a32655d3508c7255a3624dce248428f84043491233a566d392e6d3c9198a73fae7a727d5c58acc5092df74cd68f34fea97a124aa8611136afe204339b9fa8a38e184c9723fc6e08cd8b9cbce76698ba72bf92697bceb7c88a1a04555345f357b898f3014c3263d4268b715a5c2447c80a1604289a34207f5e30ba55332c449d9f1041d99051f5e287ccd7ba6ea99a04914c747174ae3e22736a76912c6b4c907174a72eecc310962ab57fbc716ca65720c7ba208b550fed8fc68aaa6ac64ed230b1f1f58a081808f2b2ce0c30af551051cc917366850610a0af89042023ea240c306181110f980828c8f27847c3881c68c8f26d098f1c184057c2c41093a443e92f0858de4e30309313e8ea0c3e3c308313e8a20800f2214e0630803f81042003e8290800f2010e0e307340af0e183f259c853f25c8743c71738de6cefa30725131f111ea3721a7cf0a0989a35e38ecc5ea3361ebb2809257f92e4777451dc121e946ca1af81472e8a69374b8811a573aa8c8b929f204caaf0a8c72d4a623dbf624627e143ec618be229b137d3ce8ec9985a94a4d02254fbcc4394092dca37a746f39ba459945634a86831a92c4adfee73aa34e6cc61138b9292d3eb7d1ecf9a1f58144e50aa4fac737890fd15e5ce38a2b4957e86125724bbae7bf3d76e45c1daa44ee277cd8ae269d3a0f155dffde72a4abb73324b9f3acb58a9a22464ee4937a95349532a0aef71e2e6aabf5d4e5051fa0c27d4670db92de95394547ecf5849f32022539453cfcf58975b8a92f67c3a75f690a2f09ed386e739259faaa328a689d19c46aea7b94e14e5fcd4a737b6e9f0084579a38ab0f3a0620f50144bac95b1f43f71c79dd331e7853c519031797a69f79d2875dd99e58cdc8dc9e144726e26f1b4ef9b285b6dba65fe3f6192ae8972988d87926da545da9928668ca80df7f149f38b89b2e8a876b63e2f51b2912597922126d57e9628da9bc6ecbb17911bab443906599ec7a42729cecde141897257084dd25a096ad94fa2ecd935b7e794cdbf614994736ecb3d99838a6a3712454fcab6752ddfd30b89e2c9f1329c2c329bab3ea218cf43499ba7744479c64f2c5525265964641a3b3c8080116c0001322861ecb071c3cc40066964240c93238781814723caed66a3cc54073f25c38882f092e4e826bd069b17e946ba51bc483752075240c3782ca26c6a62d66479be1e8a608f44a80722cee310c5563749be13356b67507a18a2709e41b489a1bef5b5367814a25c77e3f2334abccbb5081e29692f2d1c44c1a4cb94498e5d102531ef093d9723eb9607a2bc27e3565b2bdf641210e568f7fb72728cf79ffb434193d4493ed93a3f14cfee479baa4d95bf0f2579bdc3247dbaaee4e6c187a299ea49659d79b44f121a683a72a4c48b3f015b261e7b288712cda4915dbddcb741e0a1875b81471e4a729de8ed9ff66e27091e501d4553c8f0d89dc71d4a1f93a053c8a81d4b7e6bb07d916aeca081be30ed61871c498f3a1c1d4a72c796aa5fa6065bd9123ce6502a71cd3e6164b4c94935d8e450d40f6ba3ba9471380f3894d4e724ea26cb4cdfeaf18672bcdde68d1d7c33a46e285d5fbd670fa623cec481471b0a6ef28853d2d4c88662bcbdd4d56af924de494772c0630d45afedf5202dacc186f8031e6a289eee7c5692896ab09d471aca7e9fe6c2c4d618b6e12cf040838d9c0b3cce503ecf72ea9b83699c2032818719ca23f4273967b030c99494c1830c4553db64e2c70ba5a311468e1d57028f31946327dd9fb226958a654003c70e31943fdac6f7936fd27e57830da51a8147188a169faead73d2ce7c028652dd67d3b1a3ff8592e7d5f7c9b16dc2425e78946c25d639c977a1e8a22b93fcb9f927e9b950bc12f4a8c8eeb750fc2e8d53b276fa6c1a2d144389257fdcace5b6ce4269be447a149bb150f252999aafdc2b94cc2fdc24bd415e6c6b85727af7369da327cb5fab501af94ca1268918d3930a0599639acd9a7b5227e1144ab922f49b8ebe6b31a18187148a7163126e37a75f6f1885627293cee38d8e85060a2599613efe868790b5793ca1a8e97c336e07f1239a8713caebdfb9b3c4a38cb63c9a5038a183a7cc1831692c087830a1a8d146db7b12ed37c9f1584279b4cc49d3e1342859bbc14309c52f53d731271dc489198f24e08807128a3e5f92b69e93a772aec1a6630637c0c831031d6927f0384249b63e693d36f3069367bd2f3c8ce03cf0284231c6a877256544c38308c5dff97513738b8ec975c26308259d392dddb7a6613b427808a1246dffee9fb8eb22e1118482d29cff79f5727e42878407108a39c969683eaf175d5a8f1f94b45d4e54bed66692a271100d1d666dcdc30725fd7b676da5d37bf735d89c032323347460606484860e2f12101e3d286e1e0d21c2e3e998ba0b544003089800022ee06ff0e041399b8aca3d498ea977ac5de72e0ab71b9e5bbe4388f97451b02a1d3a4a3cc63ab928c8b41a1af4091d64c645b9e4fe31694eb86f32b945316747ffbb098db649b628d90725a679f82449fd602dca26095a671b935c722ad1a298c1c4c6344dcfa238f284b398ed6cb208c9a2b07173728ca11a1727b1289e70a3e3bc29f17444b02898bde6e4e163623253bea2389e4f49f275d576d4150513f98c7766af26795a514a3d1ba5a164585112231a36868ee9f36e56513a17f99bbb2564335745398bd8f0fd18b3a990c15494f3b56f4c327936284145595b664fcacca728864eff6f628a2c394d5190ff50a3d4272da324b314a5ff2034c6cc31294afaca32e357ab4a7e475112ec4d7ba4898a82d1941e4fd2170a4541e9fa189966c24aa3a028e70e272e4cae2a57fd44b9fc9490a74a14cd58f244c994d04ebea14a8a5076a234a2ee4cce1742960972a21cfbf6fe66bfaa3fb7899288cf41ddc7d1d4b3a326742bd97e2af5cb44f1547a7d4ea2f39912c344b14d4ff674ee319420bb44b16c8479e8f91fcf181ba41d33f0a2c60e1b3568a361004b946e44bed73bc8e0a355a2584ace91d125fdae579428471332fbbe841da5348982d796161dfbc4644aa2681bc424a557278ca891288e2a858ae6becd251e40be50fe1c3be72462d76003c38b1c356820b32f6c981a13e0168078a13097a5c524f7185476e400d285f287ce583dbb9a199db901840bc5ab6b4d53e7569fe4806ca1f578f5f0baf46e1db19ecbb87c173fb525102d944451a6397bb24f0d205928c84f8212ce7e4c49b2090b4513253489a9a32b14dc2d74d2192d959963856275a9d3529dee66e32a1464ed548c8ca6e4b01f0815ca9f44e79960029942d994fb7729afda4d5f0ae53d614c129b7d724e2a90282cef3557b76e9a25f3fd3996c56e205028e90eb725d7aae4bc19c8130a267e3815fac49d5056175dfabcd489e1bc269436eff4c9ac291026143bc97ed895d304a600b28482faa78ea5d5a4149303a28492ec59de6a3b8324a19cb5f24fbfbf54f30b8284926dd665d261ffbc24811ca19c4d4bb6d062f2349a18a19ca5afd2aa4cca248d0a528472de9aecf721e420442867d1f462e7268901902194aa9465cc70726aae580805fd0fd2d44bab5b29418250ea3f294f63ac5ebd1b04082559a1565726f64e3cbb00f283e27d105e1f5ec3241182f8a024dbac4ab215dd81f4a098aea7a19f6994977807c283f2e899b61a0d426ee66e1705d1cfe6a6e3ca4aaa580532745172517ebacb45394b4e9dcfc6940519b828af686c9f9e26d5e1b94579c3e726354aeee4bedba294e95edf47666b51d2db1ca3e9abb64c8d1625371193ab7485286316059329c6dbc32e4316e54da5745f3f4531c88845a9454c3ccae6686f2219b0289df4f93a9a12eb9b9b8c5714cd45dfe68d0c5794946d3a490cd1e91964b4a2e49ed4f6c5c6cee9560519ac285cb6c764a29eb03eaea2247f5ae9249c36d3b165a8a2d4d946885ea96c7e2f15c53cbabda24db59598230315a50af78c491a5d1be4bb21c83845e9ca635dab29bb3c4fae0c539404a1fa4be377b94908324a51d43a613b87f90db75f06290a23ffe54ed88a8c393b10648ca268e31f26bafd3aa8900c511473fa1835898cca0845f18350a227eb1cbdfecb0045d9a434f1dc38f113c5b44ee5a7d751836f3c51b8f594677aeededdea4441668893417adf7eb832385112e2acb4c773fd0e4fc6268a55a3dd257a902236265313a573d31ac63d6a09edc944492db5737478fe9c4a30516a4d6af28afe5ca2d4a9046919c44be5c9c8b0445957e39566ff16175d6625ca2f636b62862ed19ecea444594e8635f946ce9c44a973668eea26f1f2e34c4994e73794d8ea2945e73e33122555a74f8e7ac209150d248ac9e2648ee2f6238a952332c8242fc311a572b54eaffa1b517055156d3a9597bc31a2dcefd5277aadd996691125d1d119ff2cb4cf9e88321451dc1639b965ba44144dc88779ef9c46e5c911511819fa4bc98c5b907188f2986ab4bf924b33b788320c5110f396a14d5092a41e23ca284449b2919ef1b22444d932f6ffbf6809d3a24114a49bc99bdc4409a2398228e93c6679aa71198128bd959fff6a79723b6500a2eca5c48ec93e7f7628c9f843e98469bb91f1c8f043496934994d468ba7967d28a9a9b23b53afaecdb80c3e14e4dad589a2a1ccc5a6828c3d9494162d5561aa6963848153197a2895d99be8498c51909187e2ccd789419c6b1e25b604197828071dba57773bc997ff4ec61d4af2893186933b47861dcae6a743c9134d96cca78c3a9474ccd164cf4e22830e2511199d467dd494d1aec126630ec5ff24bdfe733e4ed0caa124671ccf10fa8a437175bcecdc4a666f090ec5ab389d1ff4fb8672dec59d1c5e7b7b536e28e9933732c6bc1a3eb4369494ec34a194c72463ccb1a174379a94a434bf8652754753cd93da4f5743492cb973b8c8d422364a4339c7674ffa3a5a9ed4a2a15892f44dd1d99ca15c72f89e13cacf5483cc503c513c89d7f15f829dca5092ba4c6b12e9b9276cc8500cdff60fbe4193a4248da1d4b59d2fda8aa1b8315d76aecbc350d2f03945ec2c030ca54dd649da2064b68c898c2f14844e25270d6fa9a11e195e2829915de2b376968a25a30b25e5a1a38b8a51ba1e86820c2e946d841077824942999a750ba5ae915a279af426c8d042e142b45fbe9bef4972441959286cde1ee13185300932b0501cf9314d4e733f4c9016645ca1f89ec3956d5677ee8c150a22b398f250929f4972aa5012fa73fcff91e1cad4543074c4690a29ea299457b3acf6f87552af91424949a13e4267eaaf3f0aa5d50d9a2413b47a3e29148af1996a4bfe89257bfe09a537f939bf3e57764f4e28b79f9053e33937ebd78484f73631a1e47effeb7a270923cd25944e7f9c66123ed5c7aa8462c896b4a19e4d4269b764d50d5d8284829df420fc4b76d8701ea1a4eb2f37ea4e2394b59372fb24660ed37d11ca172626939fe029534c8462a8ba87b8bc10b1dd10cada269e5ffa2784c27627993e2e5f69c241289c0ea1744f4e13358e8050f8cee62d9b8492a1df1f14740c9bfb4fda07c5e0a5946062667a5076134dc97691cbe041499bb42fbe1da7aa25bb28f7bece8ae6eeb0a7d74569ff772c4e664f77712e4ac2ee4e858c372e0aefe9e9d1e47e8baf47a76b87756c511a6592895d7ad7a2e059b2694f4a981605a5243b25468ff5ebd12c0eea374c106551527af494e46a23c6a2b05fa5cecd3a8ce71c44589c639f2c32534e315e512cb1c7f3efa6677d4f1262b8a2e07135e9abebf220f318ade818ac28f5fe78fc90711d637c15e59c4e8e41c55655146ddf64cb7b7b12833c1525196192e0b7a98350a2a2985b723e6b87114a7d4e51d01ea6449f0dd79e39539433899fe37bee1cd4a64b5132fdfd277a92cc3c4b8a6252d2aa5a6bcc49cb388a626649ea4df81266ca248ab28c6512c27d3d683c43519264d5566b5e415112133a09a546346f27f944495ef772cfd7149f644f14f443f79820bc189d28d665e8a9501d278a21435f9fa04edff66413c5faf56c26af26dd9b26cabd793366e9ef9ce32813e5cc234a18253c4bd02326caa149a68ed194d2f8da258a9bfbdc8485c6ef972d51ca36ddf8499b54a2243a29ed4a8f23d43c9428f7c85c929ce5bd32bb49943b7318e5a92949944ffa6463f267ba3e51248ade7bb31a350b89f2686811a6a7d442ad8f282619f6fdb64a8e28e993694bace3334ba811a53e25b6d586d737b16684a7a2335d44f1c4a8399850ff1983ac8854ad669d3aad1351ea581e4ce4dc9ccc258828bddde792d496621ca29c77931c4df29f7b098a6188a28e9039369e14a2546922cb5326497eff310851d4e0ffe2b2699289d0200a624ba8ef518fa39ec51084f96fc64498307e06a251e53b621902a25419a388ec127438298df1877de54caa4d3a64c9c7f043152246084bedf39c1a6c17a30fc5d55d6e9859dcb8a5b96bae1f1b133bc80fc7e043c9b39c1893e469be598ab1879250f91e74c9223d1477d3cce68c9f3bedcf4369f6e48d9bb388cb128387c27a9834c983e59cd21dca1fe38970dbd81dbab643d9e663ccd3b3259ab37528c69b0e9364eefbdb930e59c9d4bdff1c0a379ea3f664e5a0598c55ccc8e8b556cd28d1e492d46a3a0ea5b714b9fff81e9a4ee050d2240916269b9244e992371443ef98463329277b891bfc244369b8d69836944a949f1cb3a82741cc865207bf101d54e9f053d750bc92734e77da637b490de5301127eebfa4ff36d350568f9d5f366631d0f08693995bd3a8629ca11cd566abe42b71529c662809cf3339537c31ca50d2294e267dd5c520433974aa884c952777b43bc618ca3b62929c73fe7cda670c31d8488c30149424f24cadcb8ad6135a186280a170a3ed253f4e31bec096cd557ec7eb16c30b4593255bc2eded85185d28e789f7b9fb6a262a1b1961440c2e94cacb3baabe9d85a9636ca118b2a4bee7248ea1855249a624412e565468c79b4631b250de16257bac667c743c31b050d26e9258f7b75ed680185728e6b0994f940e32f54c6258a1984c990cde7756a1acb98492af9f4baa53e3c92006150a42a8dd935b62a6ffa631a6e05f783ee1779248a16067926442d865d694a350aedbd82ac94a4ed50485c229bd7b9d3b339c376ed88801f620c6134ae29c184dc912fd20a7bb410c271483f856d66f4847138ae7ff9a3b870d9e8493098513b4798c2bca1b333049424b28aae6d4efebf969e65a183194500ed2844c326c94a7ca8883184928e9c9c613b4bd55735368831848288a88ae9cde6c92b3c311e308c590f2259fcac4644a3742a994542ae276735e534f478c22945c6f3cfe83aeae5012a1782384cc23d44e36874937be6fc41842d14d12afdb1b474c73f72286104a9b93cef14a0a0ff2e4bc2008e5f89c7973f22e693aa363611003082579f3e72cbde9481ba5de467c694e17ca49a7103949479446b950ba0e2d1963f292771d36da74dcb0b163060e10d942f9939ee6249a4a12056820a28562aac949dc468ecce9ce4249aa90f9a5197b37c7582867eae9b0b626c7daf20ae50e6b9dd4a42695ff5be14acf954bd3ba7bd50c72c32a946eabc47ffed3399550a1aced26327c7fa650ee346b72f8d448a1a04d7c3fbdd255d1a14814ca721b4acc3a08d18c43a13036235f236493c62bf284920cef9239c89c2fe389130a2ef6be75ff19f763449a502e6f4f2df798cc348709c5cb5ce7a72bcd52c72594e450629a7b0d4fa289514249aff30935fdf897b948120a4ae7f9acfba5de4716414241b50825e652f2224728e6f18cf13137c67b2b62844db74bce4e6cb75cf5f4fc735a7fc8ba458a50bad5ee241ecb800811cabb49d4d9fdd1ef71141942f9f336bc33496b828e611aa62242e0ae3bcd3575ccb5ebc35e283d42224128ef08a5277ff3a3230242f1e54f68d9d56bb0991c33d86174e4edf8c2068e0fac99210d911f94735266f27e624510f141f1ba4e378c7811651f911e1464ffa7b6938d31453d32827610e141417c5e6e8fc63cd14cc82e4a17fa2faea4f35835d24539c642eef45a673521b928989f68e6a7eea574878b82afa96cf19d9bf5925b244753f229ab726d51d8d3aff9049dd49d94d5a274bf7127a6ba79d3295a94eb4e8e5776f42e9b675150bfcd4cc296b2b063cf754dd776e5ad2dc3946f3a9da3e4d46351ecf02a5f1f7a766f9a542302382c5053088145c1545a96183637c97ee515425e51d07bc29c4cdf1e26d3ae28a6cc2686d2dd0b21d3ad2858085582865535e14fac287fbda9fb953969c55b45399fdc6db2d99f762f5520441505fdb14a6ec80dde31c3869054942d3e5f975882810ea480068e1054146f378406258c8bde1ee19840e721e414a5d3204784a84ebd6914628a62d0d0719390c6990d300c0d2f74981a3407424a5190316689e68e93a2fce3f9437309fa356821a328655b7d96d62e3d9328d8b2757b17b1b78c1f694abf9de4261a8a82d83cf1a25386f4d98480a2e8f6b9514eba219f2807f51bcbf8d9cf7f433c513825db58e8d589a228ddf1c2a385d637cc1739d20e3e1d3a72cc60023ad28e3024e09c289d980ea31fc2f2541bb209e77b444982aa93261d219ad845c4dcdddcab624f3b339e52675d6562428799b8104cb49fa771f566f2a199c5d4c90d9b904b1484963e379974d4dd6e89c26997151d3d5c4b682a51facc0f639bd3ddf4a2045ea376a59db6e2597726089defe4924bc49c4461b38e1e25ba9960298648a2983b6927ef0f25bea190481444ce448426314e928443205178d33cb2ac67d466d203421e51927e55367237e7f02347946466d9664d0d25c81ed28882550665a22991ff358730a2f46153f3da5d791e6de6224a82f8dfa4398bac0e934c4594e4d4e8662685d02e32c44414534d29314676f21d250f4144d9e3be8a9efe1384ed87286e6c91351eb5f32879288418a2e8e16cc65c733734ee3d08294451efd7a4dfa87949720851ea2d492b7be389ed1e32886297b97ffac6d4b65510c5ffe0b91a36dc64dc8404a224dc2895fb61bbb64a2180289a2475c6f0491bf28782d6cff7c850881f4ae2e59bdd270fe23f8dd887d2951c4df521848c5b08e14349a84ce359478ae9b4410cd94341c6c9ae351e0fd143e974ba3399531f9287921cef0f5fafadac31082178285b85759b7d521b97ef501c5bbf19134fd212447628c8ad8ce727ed4e50e23a94b4aa4eae1b910ee5340bd99a4365e376e6500cbb7fa55743cfcc54851039947aee73ebe383ccd81421240ec552a35ac2dd835e4fa9100287828fc6b0fd9341dca76f28c7125fed4b7e4f9276433983aa3a21bdfb34d73614436df63e8ecc6ca76243d9ebac640de50cb7de4f520809216a2846d3bbfa1bd31e084943494c323267ff0d32ce090d651f194deabc4975533e4349c7e8e9a7d733611d33944346e8a09d3a34fe2a0326baeed615ef5a756d69397b4227133c870c8c503a68faeb3b8662923ec6a4c397ca18436228ba5ca8a75f3f0cc5f864f287e9f89a660386d2cc082d3ae612f24ed5215f28f868a8fb8ad80be5eb70e25ec649178af1c43d974cd326b5e642c98496933b7c49a6e4e5215b287b92844fdbd2d142b1543c8cac7337f9b31d21240b059d7a9ba945988ca2c5427964ee1979671a33ef902b94846d9518647718930bb142d1d48fc611b2e4fbdd2155287aa8f8dc768d9b5e542a94c4fca3b3bccee6ff35640ae5f64cba721fe5cdd31029943cc92685bedb8444a1f4254f9fb8957d1fc41028145bdbb36d4f5a71cd439e50ba93df395274520e1b14b04288134a2174468dbb18597aa24d086942a9c47aebad924129796d258430a1a083f0ccae494f67bd6a30762005344642965094b70cfa4d946ab0d5d031831b896620440905d3281a54e98b9067d6607b018e1b189284620e6d25c9d1d4ebc48cf922318e0d20a1abb84bd52f57ed7477ed2f4943ebc4d231492f78018e0d8081a30323232fc07143438e50eeffd5d839978c50104a2893791aa54048118aa976763f9c44285d8fce12ad5dad2f838290211473e3ee663511f2632e849228b14b84ce0b13f50e098297d662b1dae6d696997dd2768a7febec307608100a9a3a8e0739579710f283c29d3ce19d94185d4cea5808f14149caf8892729f9fe39ec84901e94048df13eee94986ccf85101e944bfe19fb5d0bfb18d500d94531ae96c6ec989500a28bb2c91a6312dd7cb6f4e7b928e620aea683c7677bb868f33ec5f36b64f4d23d5433eb6f79265121f3050e1b5ed8b8e120b728f5ba8c959c45ce44df16a593ac2499eb37b52849aac3549c8b12ed3b4600a145498999361ad24e3a4c8d0f300d0b03641685b5d2ef41b72a8ba299bc994e9ef10451e6485f00894531c86a198fd1438735018145b92f45ba8f277ded5f511a4de12b52abbd37c334cc15c5934ff55e061311bc0e1b5e24482b9283501bc62cfc1c660616b8619c15259d4d7c338d56e357ea551494524d22a61642d3abc1a68ad297d00ee2754c7335a928cdff06add9e52276b20e4050518c614ed0a15a66f7740a198098c234ab16d5dcd1b6b85c93c4b04b51da68723cb9c40a1dae2245e14569937f4209c828ca697e3e5dc7b5d4b841445112a37c8d4932588bce86a2bbbd31cb5cbd7299570f26e79b4b0ba52350147e4bf66cd289cdcee5274aa2bbaedc98209e287612b78474fb309e459d28a77bc6f5bb5b838dc66e200212f0c0062cb03b6c90767c81e369ac71a2248fdcb425c9de0e1d386200b28962abfa08796f16ba7e4701441305fdbc0a694af85cf24787b9310197024826caaaa941296974734991f9c0c8080d03c144714cb579f6a46aa1f49728766e36fdf3b6f145098825caf21faae4173d8254a2f056f623fcd449aa94400184124513e166268a6ab609029944297694304ace410b2347f7054024515077214a9b9cba9478a11bc946054646746c183bce0b209128e7b71e53e9b1a4062151cc2155554598f586e811e570be994aeca9d37103e288f2094235ddba8c4e7dd2888289cd7ca8944bdb9f11c538cfb6d1d3f6dcf9220a4a6d9d3eabd5543a2ba218375ed207b9b81373798928f9865c784751d2c37a8a8882cbefe92679276812563d4471ecd3a49b983a8f980d51d87e8d9a4449a96a21ca2574ceb61d97ee7a6a1a4008512c51212343dc54add783288c69d90d76662686ba0451928412f79817d3a9633410c5922b931fa46f8edf8028aa9bdce3eb39893f1dd53f946563efe951e29d0e0dc78e2f30303212468e1d3a6cbc20e5f001881f4a26eb4e12a4cb75ec3fad046d3901903e144dcf4ede58e7e7beedf8c22c1f8af1393bb2346934181949a301eea1186bbc44d9ca9247da1cb0014604d468a046036403881e4aaf3d5f1ecaa2745e7cf0d4776e0282874c7fd32c766d903b94ab4c099ff1be1e80d8a118f79d444d3491ef79903a94abc64e7692322074e0e4ea8469de8f39d3207328a8e692b9a4aa92ab1518618ce0032323378ed9910307188d0510391484899afbe1dbffe4d1c8881840e2509017699a31ac9c5af10361ec0863c75d01040e9ccc6c6db8a86dd6568d8bfe390b4d2f1d1840de50d48da3f9c4dea83e017143614ebe927d93aba1d394e30309d286d2ee954ceb7e15669240d850f224497afc3f644cf2bd86c27ada5bdf2e13b6936a28b9ade9f4df2bf9b408928672492174dce42428b184d150ececb6724f7286527a68f5384de2a8d640cc50fa5c9da579739224e945014819b22e7db396cb9ccdcab60ab91b435c329483c9c5472f41a6f0fc1709640cc53e416ace4932e95d8f183c133911f3b894176ffd2cda774be82409da3094e48866feb79e3eb1beb091e80002868214036628f69a9cfeecb1392f4349cbde079bf1246a92a12c3ac58927b66994c6504cf5d9caf327f9491443f16343ff6a34f9d10b43418c8d7f55cac61018ca416b9650d1f00bc5603ac953ee412f143c6e4c6b0d73d5a42e14d46dafe38bd5671217ca9fef9318634cd361da42e1c2c7e563490b2541ef62ef04cd12cf42d13c2613a733c81863a1709acf4b26a5f55d7485525f27dd21f3562886fb15b59a5385d2fc86aef6b0a2939c0aa577d7ef6aaf3f319f423183b4d8183a278572e5ca565aefd6b517859286bed3f4f3db3f0f0ae535d1d3bfc94dd7dc3da1a04c18ed9dec5ef3764e28af2641fdf5f3a6d8ae09c9f5b16342514fcc6e6727f163b484a2897a8dd149899e7f2594e47ad73f3d6523f3492869f66bcfa5ba44ed91500ca54fddca8b34217384f249a2e77f527c148d110a4a66f9bd986c21572b42396fdde9188412b755234271e4372971b65f42694328886c759bc999108ad19424e61c93582a0a42b1e37f480f72d63c09100abe3a3af4930e17273f28284126997d261f144f932e31714c9f7f801e9484cf415975d4fdf7003c28675f3de9a4162bf377c1ca09be2e8a1bbdb54a2e4974d4e4a2e8d77165548e8b92e49bb673ee5b9462f49489a2db99c4b628a9eed30fd98d49676b5114dba0e46aac6d132d8afd7f2f22c3f8c8cca264c266917d22d4c9aa2c4a4a12d3dcd13fbe83b128888955af76b028fe7749ba47af28ae49b37152349c1c734539c878b092561453ad4e6a69062bca412693ac24c964c62acaf3a394789a255baaafc1965e9003477268e7aa2856f95baf9bb8561eeb9a918a927a9cbf37cb74062a0a329c1275cf2c3d8612d55394b3e77ce2b57ae9983e39c314a5971367e489210c600e1b34c0d091368719a5289a28414613a91e834e328314a59149c75c7d255c09f3288aa73c6cfcd56d12bc1545418ef9ffa6e7a431d65014c7d3d9fedec8e634ce0045d9cc63da8d267fa29c6378537b0f2f08333c5134b9d94f9cce933c9c086146274a5ba7a93fd5a91739274ae2c27ffb45661345cd6a66829c68a2549e644e63b9759d431d99289fa432c79c3b87cc66c244c13c9a70728e9de424d34b94cfe478d0a9d34dc7184b143fee97246e095fb2a54a1474fc28254af2e329a52d6f6338d3248a26a8d52cfa45a937257692a8744c4aaedff845c22caf8c8bafcbcc6640a2a0252725ba4b660d763fa2ec26423f9d876e80e10506c290800e1de6033f32628e28ca261d7f54f56473dc88f2bce64d723849ce378d11054b39f94435661125f5257ccc3e1ddbeeafc1b68a289d34e55b5b42a912ce1a6c896846224aeae733c70fa74f4bfbc00c4494a418956d2b0b59265a830d2fd9c8b1230f51ee12f22491bab6316d88a249db498d495b0d362fb610659fdb9884931673926c924e8882892935c9f9bad2cfe54114c634adf4478931ef331a36c051831d2d88e27a8ac664258fb867f502512cafdd76ed58191b5483adec055fd8a8a1c3d4600ecc00444136a3ad7fd49c4cbcf50fe530a3da4d78925525a9cc0f255b2f0ddd1dcdf2ad1a6cfe3a6c78917d28a6d298a64c30d9b0976d0433f85012a72709a34e7c2891ead843a9af3bc913e4c44c92e9a19877264f4306714f661e0a3fa7a49244d66c4c8235180f25bd262664b0f90e2571691ee5e490f13b5866d8a1a03bed9efe2872b3dd3a149387cf5869a1379ea543d94af3ccc3c8e8756a0fcc9843314bcdc46fbad5242b95435937e7e6f2f7d62c9b068d2f72dc0566c4a16876b2ac7e9998736f376ed8d0c0c8080e1b34c0d0a170405fd8a8c11733a0f1858d17a42380196f28ac26a1ddcdca33fcc80da512a49669d28c2d199cd106adb3765c4ee3ebf6b624e126edb406931a2c81196c28789589b44c4d621499196b280793ba57b23769c56723497206374c093050810a8c8cdc0c359474d07792c54913a1d58c3494eeb46c940b1d0dbc75a8c766b85ab57bacdc76c7757bdb93d367f0247d628dd68f3135c30c5adc985e6dc7b7cf9aa0d3e9b01b3c2e98518672d75e5dedc9e1248d93a19449db9fd01babc3555a983186624c279949426aa8927a1d4498218682b5bb991c113a97492e0cc5d1e26d2eff926006184aff497cc6541d4d27fe4241cd49f7dab02e1af6195e2828c9afb232fc6a841d82195d2807d5a964f3dfe7b26770a15c6a25535dfd334fce164ab277ed33b4a0cb8abba95d768cf7e954219394a1429784195928799824a704b5f179923768243070bc0eb45481195828d6edc94db6a13e06255728867cd3d1f74c073193154ebad7375528099d33a375cee0c1cc195428c694efcdb7dda227c98c29b81a5aaeefaab55b3bf7c17474c96ef26d7c86148a27fb397614e5aaa504258e4229e7347f6dc93de97fa6614607faa43b980185c2a726a957a3abf5d68c2794fb241bfdb20e223c35c3090539654a12219e0d1c25581ad684921457ba69b1a9c10c2614e3ab986cb9bed924c33037d2054646900d528d194b28cb891f19b92e66b5cd5082b7367abbba79e99d1b9b1f4f3213bb8327abd28b1949288acc7ddf70531eaa9c8184820eb293f5f999f84e728472ecbc61da24f1f58c5092bb214e47e69358a72214d53d93189359c2dc3e8308e5b23053da3c4fd02447c28c2194e4e851afee5763fe720f6608a15c32cec5fe974128690f2243892598b7d84028c924aeae2773db2fd98c1f9447274bab0f194684b855878ec6b1038c3072fc3260860f4aa263b29b4fc2f95a23233b8c173970243474e8483968b4ea408ec18c1ea04e77f860ab5bff60060f4a2f2ae36dcef50329c72e0a57c29bce6c2ab77545745136493e88589b45725166aa5c6b68bddcaa665372cc5662a5082e8a26863ebd9fe46f518e6de99a525f933cdd16a5d09251ebf67115e25a94574bd2923eda9c7f9a0722b42826af3c99519e26671f0922b32829b9d03a32f20e1c37443032c2344cc70e1c3774b489c8a27842a9c7c660bde3ab482c4a4a8c7bf8acb2af994404169e7610defb723a2bf20a342e652f3fb52edc56664f4e3b3ff94950fe8bb8a2241b94924508a519b3482b4a6a4cecb4666445e916ab57f7f2221f931c4a64ab2849d2fce6cfd19f04d1c006420a789f79aaa2e8663da6c4841505915494f6ab75115494ee4deba67dfe5a6e4e5150326da9dde7a62896bb8949d4a41a4e264b5132bd762f6f9d14056d576272e3f7f9dca3286bda98247b6ca2286d12be2773cc9cb39484a2a0325567aab7cd730b14bda5cee88e8bc5dd9bcdc9cb9c7429ad41fd094d90499df613634646aa20e289621a159367279cccdd1736d01733c0c197db9d28996bad7f4cd169e18d8c340d4e944da59670ebe3043651925475bdf66755359188268a79afc494f5fd8a934cd808264c2f6123229628772aa953995279caf42295289a4a134294d49121935062d35ae6159b9426c1ea6668ea55c678d5e59ba45398364d566e9244a9475b7cadf2b2486ca1646e0689e298c91c79ab0aad311d11794449b0f592e4ef501131d760a3618354832d8788234ac2df8852ba57b33b74b84823ca364ae77ebc0e8d18a40406da17228c288bee1863c7cdacd9fd46c0082c605208d2dbb82182911a228b28868a5f8f6b9fbb9583e1858d91111c364646bec0a1030634b405228a280977dff91f5293683d1105214bd89f0e6f15999374208b20a2fc23deff74fa523a260f510ef11ad373d0a618182fca54c410ec5dadd88a58ebee7ed7bc0793ae329a8a931a6c5ee0d8a16306bf35102944c1840d1354644efef2498108218a394a64e4751c573f890ca2bc1bec4e7e9f2cca4f228228a63c65db1b736ab0e1b07127120816880022edb091c3047f285b9727a9276ea3a13581881f8a9a42555ee329d287a275566cd49817e1c36154ec9c52377e61e36c0fe5fa2469773ea9ea7ee481f485074620a287e28aa692bb9392c29987b28a2c413de7524ad398be2081081e4aba2fe2624d4c3207e51d4a97fdf9a5ea59262f0c88d8a12c3a5c7f971426326d7528dec8ee133d4b9f7adae9508cdf4e72963d615f631b7328a717d95a1dda47ea470e0535cd7f259e395ddc3814f4ff860eafed24281d0e997ab4366320f286f2266d720c278e0803113794a35f87f7283f32657403078ea37a43a40de5f513446689d85012a2836937c14e67c3226b285a6813b3848786306d0944d480756dd5c8d957b5876946d951557991034705bcc88123e11c3bc04843e1c62d94dca413414341094aee5eeb3db34e32226728fbfd8e8e8977a208cf0c6ea41c60708b98a13832c9b2a14ffa128498460dd2176b1210294339469ab9072ff17a823576980f648d1d068c0c63c7a50819cad5ceddd4f48e11d797b992c3acb16387c8180aaa969fd4c888a178aa36dfc9d341aafe226128e68b29b935f747100143e9a450e246448928b143912f94637e9334447cd7c639125b8ed41d10f142f9248cf8a8342ee622e25024120704c280301406087817631308001840200bc582c1603c8fa4e5071400045d2c1e382a28202222141610200e0a05e26018100687c2603020180a07c3a07060889848a13cc84a465924120ed7ceb69ea9b4098b722d7dd09432be77f5e1807c0cf37d9cabd472e45825b01d523c9f625f3015d69ec37630284183e3934398af55e45ba2c7890e55515dc24dcd41c9f84d1712d2c96bd9bff8e486f9f87580baa23820464244a92aa08ff6605deb60c245f6e102f245f86bb530e918290e8e6173112a6c90ec31612c66379e4f79b63327ad8dfeffe8a6d3caf1eb006545814348321f473e05797a7aaf45c4eee9748072549d72bf545b9de82a852bad73119274e0bdcd53daf917e5dad18b328b5014ab95d61e3a8254a13a29aaeb9e35d053ee0e780dce9cc2202c11ae2d7839e9cff3ea112d7fed0fceafd7c90fa108b3db0353144633c05308fa5226915d5774e21ccce31292bee1756da22ac3e93fc8b373d89970c601c2aab0266420fce15b02aaa558ffe3f174a17f8ebbcd1f56ef873933a82c69963386035ef8c846391bb4dbcc6abe3441d2ce5771c476370a6d6f79bb365bd4b0927fdb1876b1577ae1f46a73b69b320fcabbc0cb36e4187cda4229b0259ff42178e4651e0b0f7145aea331f45a2a46c09e9092a6666c3d523cadb41dcde8ee927cd38d04aa6ae07310a2878a4c6d074d2b08e5d82a0a3f3a5d4f594cfe2c8f439ed132eec9052460439f23106b1b386904f6de602d2aae1b7f959ba5c9adbd8acf26738e272adcdbc23ca535de78a5aa8780edab7a37057da4bfd9e40a3fb22259d3fb0e7ba4139275ad4f3fa5272ec2572dfe1a3c2fa1872f6c88bf00f377fcb2dabc820bae4a30c6b880dd6fed190455f501db1421240fab0ba7fbce9d59800ff6f4819111f35be8aae8d0794f410b7e88dba392d6820eba407a34b797c5c7ace324961cc0e4acc67bd0f66c3e924107ff53ffef850822ebf57c21383ca3bd40bd5f8c6eebb46c4795512fd0e2a2da14154d0c57cd84e761a410f7cc68f5445d998e8973b6e4ae0330d0e72f0877a15bcc046ceb0b35428302e7138eafa5687a8bf9f1b5034ba1c0906d77ea591c75a1132c7c9155b1096b2a6e726914721e60b31ede0c2eb1a7774ecb684256dacb05c791aa73819be13f0cd744c422b8146168522b818ba6ab5c8c52ad333d888e2d54158987e84544d36c760abd0d5c16a1a27e949e422b02eb91ce192456d448426eaf1c652bf8ec0bc3d39442a53b62435662298cd421099c3930450970c4d62a37d460c25c512568d94213521e0c9bd41165ad133c04cf4102419482d6eaa4523b25caec01e398d97027c0890aa113f29716c046db743c285addacc176277816c63a73ee486f346ac2b7d6428e53084937d8d6a68365b82105ec37914bdb042becf5154d92646d13885f9ff1fece955ded89c7265b950ad2774d2310476e678984e0562f731f56913a9919ac2ad81c57d8725694c04818e64c54303e1abadc864f0d694d912894969298e8f1bc078876677518e38327911d46b9c559e6602801ca4181515d810ac477eb7ee99ef8ff24c2380c3f76c3b1e19e1f3e3434018683fb0054e9c6ae238f69728174e1188d2a6bb46ae187780b987abd86c3271b9502660f34b9043a6b6092b0b0762910ae706332b35abb588a0260cd62ff5ca4f1cf210758749902e447e0e1b6de7d0d8d2fb4df7bd98cbb8fab0dbb7aedb3178abeafebf6ebbd3080a146a46111b19e099e4c2d83de232888aa25f6501121a04a0c94fd9b9adcf151bfe7fe20dc2c908696fafd38f7e2ab03e2635cb81786eaa225be282a45741dc820ef176a50b6dd1f8cf99cae3a41901f3c1f3580b6212039d7180bdfab291cf6eeb430b183e69924ec706c148f92aa6e038b3cb76c18a18a5797fd959a493eafcddc0971837f22089ce69bf88038170acecbdf5a8105706b1ddc15028571cb4c9b09bd01d90f384c067e78151fd87db988dd50a3493a1a898ef43a621f1053d427e8b20198717adf2f7700407008a0b8be44be5cc54afb8fa43f1ed028b253f4c52745d5e3050be6f8be07afd12a011e07c2383614bfece97e44e624b5e60bcab5756867fd2311f1db8804c87d5bdd1f846edf8396b2a324ce82774f1bdbb8772fa3da6376eca4c7a5c5b91b0e2d1038321eda4b598d45c2afbc285f48124a2d2687647822aaf11fc23a55b54501c4fe13b8beb0f390676a09687ae6f9f890ce357359d51fc5b1bb1ef3bf8314ec4115336adca10e3fb7689ee97a9203915eb59a3b0c70c41341b34fda31831d8470875101fb1f3c819c9e88f8af550aa36d5e20b54a78d21999a066502a6791f0ea67564afd6205a578b00927fcf6e07e9a45583d9f981a6d640e20ba6a99b9a45723051a9bf7b85becfffb0f652525a046181dd930370e0debc158de1387e6b738510219a3b331ca5905eee3948fed9bbb8d6662926a0e492dfc8c9a92b30a33d9c05d76ff32e38a48bc0041bc960330881d04f7229a401c3a6da64863748dc1f40ede9c25997558b4cb268033eb420264806441d030c47235f008135c20db999d028aa38c8df7f1f9b5c9304c38518c52be922d56cebbdd281ad2803a81eecb38bb2c025d0be123366106a2c82cd8ceda11e7e7375fcfa93634240c41506f9b1a2947de44a04d2ebaca6a11f930412771d67a241aa388a9db2732da1974606872240260881329cf23ea6233b923acab2565438fa42704a9a873f759fe845a6e6c96cb53692fc526fa20b22a33667c5777c71a91ea814e1fbe83e324902d54713dd6a9cb15ed8443b77525f75ce922be2b61902cc3ea5682932f925ec05882610e4434a0d2a8a3cbf7874bd0f2d95dde3c9b68322560576ba8c40166fa6baae98e0eb6d211705d25f0a3285a53bd212a7e5d5299396fd8200066a88ca9d6cc66ec4a43f49e8ec9c2465040ab5ba90f346a08230b0a02ee10379d2ecce245d85c49e75d62bf865588c021c408206bb18cd67742306b9d44c786b6ad9a99da2b319f78d19a3da02b5ea597e208c0020521653a050ed39ba49b24e084e4c98d52ab0c946086387b36b320f8140963fd9477b9271a60323a7f8928c5fb44abb6a62f9998b0f672fc7f28627ab761b6258095861a9e620d50dec416d2fd0b8f0d81c8fc794364232ec93f557adda65681487724ac6445c56dab03e584a755bc1242ae5210aa77c9ccc16250c612ff66f1efe3af262d5c9ee6b89bc62d99dd7686e370052f1c6da49e500a5cf9aa90c7dbc2c65dce2f74e2a754974b01bf8dcc066429ec044494f8c7332fcde675a8ab36b622fddcfec0989740812a3456443a70a274ec6abd01e9aa64d0246915b93443e7a516db548a0a2071b1fae220d337c0a444ecfd9769c7a03c251839300210f2aa621706a21d110f8139241fa0ded38f88fdeba549e0ac8d683fc8921fd3342a91ae0c1b30a15dddd32adcfef6497d264564c6b595143ae972033c71ccec92f90c4f09769a24d6d225dd4b958ceb044db512e870b73c84d5aea636e98926b072544c8e64582bb76a2c259aeae4d0674c85530cd021b234957de14da2998278baf587b19fb987909014791e133a5bc03170fb1e474b1a8c12b4794f0038b912c5883b3190ef07cc1ad14b1f86989bb8cf245c7596705e52ca4bd0498014f880714173b3610d5279110895611f9a8dcd3b653012d0523d411993e164a11401285bbe007224d9cc86aa01763f3a4ee02a13864984482f61050b23f57f15028eee973784aa03b4a6a6f6468e3e9c03f26ae2c390946c736dc50832dbaf9740c59f954552c497a61784629b5c09decbcb91cc7005527bb38144443a2019e46e0729ce956ce4f7b9c56a012716183798de658daf4f82f9337260ad0f8f32805b2d88190a19655b74ee43a6d8af6aecd71ad32f5d0dc2312da6a2d446623c01f687bf9fe0c09a73f3bc583d22a4ee9d97eb78c8e18d44d8113173b553a8f9ac56407a39b49d394129fffa20a25953e17643c6d9d97aa7a0ee6b9c23aec54e3a842e96ecb6512c8b6c43dcacb4a03792d555018a38ffcd3c915b98904675e09d177cfe7590b2e469b16755fcd8896c462b8038bfe94e3668f4a8d0a1fc44ea95a2009c0e0e5846df663fe1eb0d1374800b4f0c87321c7905aa5c61be0c0a934206ed412a4e9b4d08ee7215b933fe0807d8b95e174297d4787d26d12df268d20a1500d787d2080601e8a2c1a7c7a57c889d7d7b08b71248825eb0297e524ca3f6d5935ce4749f8ed09c52718f8008bdd8a5581308e6638d4b97bcc8e10401d97257730998c366d829fe96f82b339854553f430ad419f89742fe06115f757d742bf6d3ae630905a9106d2addf5da2a9dff21e49962f4b37b3477e2308914605bdf7185733cc0ec50cf9b9c02d55c9e520c07ff8ad4941885975bdde0d48e9aa6539187464c02db071d575a9f6a95a9d25301e12cf88d4141241099ff670d1d1a5b1d152ccd1f1c0ad9ec0d72ab68cf6b53244f00fa33140059409d4a1f1ac604dcdda0ee6cde7259f8ae32255209c19b59c8d8b24349e02e092a48427192a2c3b8e2ec6b5a1a8acff40ad186f4a7026b17955db315090237b0c5fa68538b5c7c313b91cac115d0a76855eefa8ba84ab85ac03be9e1220f3942b518785f23ed330f913ab13bff1a092c85942334e69c2edcd117d49b785d935340fe60099e648edc43d3b17c54add1097f678362c7e2dc615c513a4942bf4e65e57f6105d6b0b44e8a7c34d6f15f710be6ebf3019b7a4af447fc6e77fb174e2b9a7c922b6b7b391ac14c344db53ea90a0e47459a134052c822d2d853652d1a2a26efda3bd70f5db50934ff96480df5e3317852ae1e2866bfd5920911dd369c33067c9065c6569c0c750f147809f6e05c09cc2d17ea79c19491443f476133d22b88db39235966be720f040a30c0aebb7124119069c7acafd02338711f1342e2f16f8a531bbc2af5fa58de49ecfeb1d4a2fb59e785ac7a076627a8a83406592d4e8156304be368b9d1a5d4d566d18a4a24bd8c520886a69b4149b7ac08215356b8db0eb952aedeca6255f2263cdd8694855eafbbd2a20b2e7b42b418a7828ae37b5698b9afc35cf00d28496955768a3d9d9450cdd6ef514f9bc781270c635c8f0e1d37c75b2f41c35c218b55147feb49a695d9528a2d406263919471a163ee93f1a0b43a245bc676a5d09f2d5171de63844ea1381dee42c152fd26d2d858ec2b4ab34f1d5713ad35419169170656e90a1ae2e2feee208c9a2063083174c9222ee2e33ed0210858992c417266d729ab583d5288093b29a1e530a7f3a95626cabbe8b010f694d01ac3afa60f54fb3222292c6660f3011116ee96720aba018bb695ee373c9bb60dd6671599078db77bbcd3f365c3ce2b88ef01b89be8568c17b384f0ae388799d0489974679e365d304342a3f278249f024d7715b26292905070c1a8e4d23f47d1f51f15a2671c6c466c2003b3af8daca0eb678c78d15c70f0ab61e967e6a7d8b35addfc2f2f363887cb9156ff632e790e6e2fa1d422f6135bd63cb0318bd1a63df3552e40793db508f7fa0ecada2ff7cf0825ea9980158aeada8600406a292f7db5d05719a1d3c42629840424785f37fc9c9417e82135449260ed2c3688fdf1e08958a4e764e30b0b05a5ca6a480eed3e1a2d78aed03383e2f07aa6976faa70bb4ed36a92b138890662f8844946ec6d23efba0689c00a2853641981fccf52780fba03a24fd4dcf40011e2353a88ced001ca2cc6058eb72672629474bc5d267c3aeb615cf5f1521607192fada480a670e2868364996802e0559e1ad0b07b2a05601a11fce3d27d095e99eee507734ad6dc2cea8e56769768726801fc13f3d01c40c1212efe19eaac4990350275eac36100a511ed82a60ca88e3bf731a257f0b66f10390c6b067a43635b6fbbd7308b2dda078189359c70c9bcefba6c0d6b0489c2490248ac9e053ed00289aa6ce618d7160958da7f66f9fe59766e6cf246f585e583082a51cf29a524847386ee83d3436a13c49f0b6484932cf2e4a69957f41f218333ce0dc5c905208dc0cba181eb608aa23f9fcf07893f8cc9434f7d163862e1aacc0f79855f20f122f998a2f634c1a88e0126c79e4b883135c41b4af65164292839e200d83f73ff1505e85a202fd9401cc2451e036195ff860ac012409911044b5a13673bfdb10716b10300f758ddf91d9eaed1287a9f94aae6b54465f3d35468b0d78d89257a36a969bc25155450e900e0ed8854753b16a8a743b89583fa24f52a25028dbe8fa2714396dc6610b242f1c1d362e44b39855667510555bead00be5a5baccf99073870d1367a527eebeb32d0c40da80449ca951e27d41cb80ebf4c53aeedbf0f2a008ee04d3e990018e2d7092c2a4f4110a148496bcbe8fc0f043d352de135b9675ca06c9e473d1640c7534f36325f54b2d71e2d54eeef8c58984cc9b5f3129dcb3717b87e85e0ca743c235fd540f751f4b4836040bc138441f88c9a8bd21ae2329604646c7d0efd663240839e1d143f660058031f85cb363c2d00812aa084f4e6392570a757417276167c498a2af66f9514f1295065ac794073d238b107c18ac480f83a988e1d782c6f21634a913cf3e8e1c8520aa8decaa2c853edf1b8d604fc9b69b5ea11ae217084d0b336e4b2da8b9a8c3680761dca802bc61aa790630d1cb3d80e1684fddd43720480fe6f21c011d8a0e0f7b9d8749571b77e050dab18587b43254f4fa2821350e4946d5891803639da942810f20c98165a5614d77a1c622067843bb40055d990ecba8122fe18b5ab50be26d006b4de9c0cc1f096cff2437443b6005881a0e534de1e019410311401761578cf93691d9fca5c63727a949bf5a09f3d9f8805715df84a735aeaed726e00746913dccd81f35495d44a28570d863496b2753d7d7432f99776a6cf4aa2fae3373ad8f1e267993c2b3d1eecc299cfe272e64b9250e3e05b3639ab648b510b2ef6f92a2ef428d8171364b63feb31662867bf5f80b464ce79e5cc2c914c2b5a18f496975f232de27ab265d805ae906dd5fa973569a56f39261d9a01d67fd14c0fff1fb30359af2428e869067958be1c4349896038db61cdcb84a87c5ceae01fffa6fd4f49b6383bd1dd72dbc254a907054ad4c34c4ea2ed96b48822f0f3451b5b8dc88c3b87062a2e2489aef45efb86311c2ad0c9dc362861e347f41858f84e79991069b5a2662f826d56f5e7329b70135759a6b106b2b8fd80ef962831994c701c74d4e6ce09433437022744cddca30112e6dbcc9e07cd078a716613abc9de5a7c92a30652e5c74c4f14b8e327966c638e9a630112abf1139912c12259f6ba401de71fbcc94c09bf680d06118354c1412469aff8541aad5b16d39bb60bd08a576d53ae1cfc32a34e3838869208239d99808caf56cf810c29bac613e940aaecfbeb389e78d95db8f410749bc3aacce58b842aa765fc02d820fc0c62317201ca1f9b3686a08d3c2f6e5ae5fb4a26dab4b215e0010642c8a08f02ce5795be2da317161b57657a9f0ee14b3fb5b52016e65c9799e2c85b70af4ec05b50d19b0b4ff69009437df6db762f847fe47257c6d0b435754922118de5810b2279664d67ae0898ff235e1073dca5c36b533ae29983efc0f08d67dae85c97ef76f6051aee294081849a04cfeebf89cdd049270ac70f3d88751f9c354aebd9caba94120ea25f81e2a2ca9bb910dbacceac207dc7b0a1a87802f48d664723166abb052d9db3b811d98238f0d1d4a504007ccad3e821ce1a4750bbe0c05f9bdea66bc207b9bc678bac4da816bc2a5ec9079ce995e884b4107194051f6ff570471dee9fd9e628ac8858499056fdfe752282632dbdf84a70be8ed265966909336f3c5c86c38ba4887c4f198cd9d7d04ced89506d039e5693db71569381f028b0c7aaa722c0a9e5fad662a4347006f314a90b69d0139dc03603db6ba5aea88f6b515a32bc7e86dc558a9c7f94a663da13862447004266b6ad1fec40316b5cd40b7a3f3f7b7532acf66c9d887f4419f421aee2e299c27579b5341619428e199861db1b7565a0139a198e0b179223bd765023479d4ec3b7e29ca85ae8650245e8bcc799b3e56f2960cc40c040318a0afe517a7ace32874a1b27f66219e06fcf0ee3909dddad8c050e2a0708fe007c23d5e6b64b2c4ac0e3eaf77dd25b7d9c1829eb1ba0d5a6f065d9a923d697d6ebdbbc885f1b242feb9472654920750a0467883c42416cddbbfd40d8d02a9a9530d9b547e6669b593c4e35f84d0be638732908643a86105448d39fc9602f890eb4503b0547004727b06033c13ccb2c6e6854bc1d35c5483286d34e13490089381b59eaa681b9a27b27d70df81da0b4c05414e03ef8ffcd08005ec5fffff036b5c8e68534190542ed9a5161f90ed0fe7da74c0edb685d34457bed81660b67e900af064302043f3b410d8664da67e78e1ab6fd54bae558c6520ab154c53d6631691bf7eebce1bd08dadbcbed946ee0a23a8a48f1240942bb6d931f3067f9d3507e3960b73404b6971a98b6a2e288c726ac3cd03b70e4760b46e9562a911a3cd98234a168aa703f28486635ac54e37d817a8bb63d362ff8db5d41b7cd235663c2c37667e7b10e3a473c0d5c620c3377f5481dbf59ad255a25493ef846e43088f22ea66cee369f488bd479e6fa718052155b39ea498f3daa6bea009324465860318cad62f5d5c9f1ecbd5dc8daeca28079f33c82e93ae9da915320484d7524f59f0733e1ce5fbb140bbdda5bd289f03aa3283d5912df7950890e93ad9318588dc242d3eb43d719712bbf16235f7575f3c226784dfc1e3795e55e70bc8ce3853e5e13655cab5ec77e41072b9297d4febabef0826b1ff442e9d5d52b8217a1e5eb180536c6b57dafa3fba0e338dfeb388d42fdc9d645a32b4221a13f91afdb9fc250e7b7f8f61e734fc45e030e287e46696200702e6222c639841876b35143c659f85cad1aad1e32fa178b0cf1ce6b243971f3d429ef7c604baf5437d174296a71b988507c410915e55227b9c5e108359da2764b41820211aa15140642e990caaf94afb7562a9062094508460143324498cd45f5a1e6a46b6f287f15ea1ef05fb02880920c550c54f780bad79b6e189a424d812a12d45645011bc22207281a059c2cecdb4a100a381a38e10b7b7201aa1f54538702628c3c02ae8d0252214000d1a280324b0cb38c1e052c7342331416d577fa40967443511305045d9e9de2c6a29abe0c8b17ab85e09fc5d70e2048e024a4d358e166f40c8f6b43ed817ef0ec4331f4131e2baf19ac9d057914a7e0e3eeab7c3db2b208e2509d71c147f86a9f3be50282713d55645f88af7a15be87615d4495bd2dc6a8fb427d232a8214a850ada00e270a9b14f350bd4061a7281d922af14b09144250ed3a0a083a032e3d4e244640b534a921543ea35890ea050509d568a87507a54326e8fe690425010aa125aa0f438b9506a8eb1054df0f7cd40154a20fa583f18c1c3d8132172a9ba1fa3eb58b9222c24b7e84c248a8becf3d2ada0475eea38072f268647c72a212fc25469197164b81e9e37f9b83410b516967b8132b6c44695f149f812806fa540762448b86a5473e1ce550e7f966e453b4efe4cfa52a7d84f6626b1ebc250b45213da1d7d8b04a35e5020c9bfb946b1bff1fe0dc47834ac60ca55d7adbf957100aaedefdcb5384b1ac4a1138699116a4586a672578886349426e834c431650aa69f7f045a5c2f5aeb25d3bb7201d5e17355380e5fa78005fed18818d43382afdac8c11d350533b9bab8845c66afba0b9f2cea83e66273351f5d5a20f94b34587d71fb1862fa036cb7e001b06c088b8a4029187c1896742172d1ff9cac5be32225871dfd7f9a7d86392fa09f3fb413ea229255274d6c46d6ba7b8db43d7f7530a7390f9112461eaeca3dc10077f49bb8f67b43176a4982c2e7b6eb905ef2a864f6781f4c543880ead514f03a783dbbf3e29852f99d0719dac27ab5282e66b3933d858a0c627d215dafb89895430da6f8620b9df8e71d433bc68efac086cd490cc331502c822bb1cfe12104b4271fdbdf3fc83462865c5ec3465acee9a0451429331a115f63509b5ecda5fadb866df81dfbc3155110201537fb67b356d823cef5c23465362e24fd1221039ba7531b49053173ae3427b5305bd7d7a78291cfd00b3adbecc73051fd6e38f133060ea4164295a43debc0dabfbc2bfe144775e04835f3fa5e3431e85c66dc4677201ac0d497898a8c126ab3ca41388604b4bc2c6ad24feed9825b2134a80de1df788e6bb61a9fee2dcc10f6232682ce7e0b4513cf63566900ba69604ce80420a46e04cfc784f3200145212b5b9c87f37de88c8c70fc4dc100004f64d31561b7984cfa6a144dc8daf0aabc5474542afa664f6b227606de89004c3e9c81d4d1098e1b26a6bd20689ffa36ca8464f4f410fa371b0b51538053cf6c0bfb83f6047875a37ba0d979909c4b79549098057a953f6168df99b8393c5f1b4a925830eb7ff085f58340fb5b7c2291e86c35f9012282b6e7b4ea26926e13f89eaa960c2a3338634375f55a0bd7141120b004bb767106d0174c5d5a129481437e7b381745ba094a00548ab30bfa58457319333410b318d92934977c406166522750b523d17b0b5b76f6cab8d15d6314ef8ecea61506e1cff1547e14212a045b86aa38d98d2e3a6cc932674a4541daf848f998421bf044ff8bb4e0cef0481d1c955155939525f0031bb15e9a7535bcb75d7a6f48d3da0229a5a9dafa1114c46a8d0b960024d461b50d03b52df4d4a61988e799b62328d20c0691c3b2ea93d990b60d1d357d02fef1a1919c4df20a36174ce097c124952e103517da305901d0cbe73d9e5721244e1ebef4a1f1e564781099050400404f9c730c50d68040cd277ab6250e2d8e6c1cdb7cd0f6a12c5fef0fae0ae31e9f1cbaf6ffaebd1ac9e9a5ee3d4951b277a5ab3752ec69e551f6d0265161d1ee233c931be066c7922f1b2c0d7728f36ac95dfdc62de49f7d21206d9c2b237c096c93305d6728114958856ab6dc3cac125dc1b9ba148b5860ce98542645847757e2a6b25830b864de20be2d971ce56894beb10e91296872a22e1294121c5b7438601f62c6e374e880f530da3b8d34fb7ca0ddb79bd8b23d8cc4b6ded4d5f840bc5831ced1814178e774b706ed296e0516529a6a8a6afb31679a5bb6ed0f8f58be9a293f1a9529b26675da05c14354111f0c49c5307abb8c1413596dc183a54017a9c8f2428b80d4d8277d95cfe3124b17aea25e28f0bdaff55e443d0d97e7468477d4b89edc0808da7e2f167182090900905a258e42fc32c9af6513789e6fb6f3ca792bdf691ab00838b79be43d6e537485af9934497d4138055c49c24f94caef989e2b738af4dc34b67d95a65f0a956ee4f81a1db335e92682ea6bcc7c72aa1dac88ebd3e8b1334ee77a0aece4e992fb6f4d822f6e8108eba420df73d185279a8559df1751521b8415875338443f5c6566d5e8246b19c690ca1771c1aca226fc26f372cd0fee2e2a87067ce6ef3c6796e78c61f491860bedb2f699699746c9d37302ffc9381982f17cc88d0e402d84184c417785a7326b8fd24ace351ec97b0f3491612d5a33e11df9b3b37b740658067878f696fa1fbab6c2eb5fb50f1b759cc8e888c3c2945314749fbfe1cd7d2c1bc62657003e5c7cc4dad6e00f5b3d2b9b6a6569158805409bbfc87eb385121c3e3992325b1a8bd50fddc237a40e0994d78c39fc9b517ee5ffa70ed8a785e40b453250f08d1c5fecd28eb121de2005a0c3b030b23fd5f0ab120eab7932e316e752ce4a2f328bb715caf12d82113ec7e4e60ffcf1252d48a78d74cb7e7739efe3516d7ff9c7b22bef729617727b4de7b309e39a1b0dd7954dcf9e3e6ae387c66ac4d0c88627619ec25e2218a993e45aaeec23e257fdbfa3c48c5c05e46d2b02dc890de9dc69027e5bdf8040edbdb7007f00a3036346c8db331162fee34b4d61f244497e9e611e2316cc5be715a1206dc1619f5bf2de851dc5cccb8b388e74d0f36ceca4f6dcf5061ecff616f1e743e132d8a55f1cf06c788ef50de070ec446dd48f70b278705a9b45e2f8c788a807df8dddf5e5d8b69f24c4009b69f7f5366441049a50099c6bfbe51cf8397458e7e2c0c058fef0e6ccaca709474eda08f08f458d76f1b43cf10e1879a1905b7ad3d7f70247bfd76583ede143cd279d19252b97e912b9f62c2e3ba249f337c0246e6ca358ce825698af2e00956159e4559d61f53526fd9e3e03621b182f0d62aa10dbd138a13586cc00e22e789f28e76b64219ea2c0a744acc12a0fa90eb06249e82680205e4db92b08e2d2ece25f6fa49d5be31b780867860c0c3cfaff76a8118a167053c1343a218e981760f90c69301d07a70dd3b5407a91040359003086903c43ecc9cc8005802206306b82b49da5c724605dae7a572e2db6fa63a3515c910b81015c4f295d82e84db335e190040b8dcee04d6ba3cc97cdf3e1021320cd99f8158116c03c593f69caea5ecea96c9eb5e07862c3c0b0a535b282788cfd4945a2ae7b1e4ef5db7210d5b42e3653d92b80e18f375849bff5c18855e0da1437c89e0b6623271f3b0026abca69fd44f606d9b07a38fe6b1c3553745062939c4eb7d1f314eb3012f31832801fd74b4437635d5504cf515a98a7a2e47792c19e511dda055a51227097872172d09dbc96ebb02348376922b433d56a4af9772e1b6898a2525372f9f0e69054bc45c3ad1ea97fa3d5f70a013f687280ede6a4b24979f954efd14695ac661642f30a7b841334365f3f4cc95e74d3701de1b71084f60d40e84b9b41931e0a048ce8c742d0c2f866e93ce3dd079219f251e74d4c36d9abb4df5ec7086c0b19e92efaf1da85d777cca341b0726d43825d1a57d8b8b3e611bffc8416075b2691e7f0123d7668795c4b5eae2578f562970637afcb8d2df4372e5edc646d9da6447b39a6187897ecc4f4dea88f93472040c443165286281df7b729579977da34c8a4a8ee25b9c7e7b93e17f6684bff827e48b8b3192fefa8edc6a276ab44d11f0a3c76a6021989eb788bec353c7bb0a2513370849c825d7b0c4f58855ece67e308c5a194bdbb9a5fdff961350c4ad163af8dba19f54079de9b08274513969251d2ac63f10eafad0955c5946961d95ad84a1aae0fe9a880c5472b5742c69406cd586ea500bd530017c1b40231523801d69a773518cdf0759d03a85876d22219e49426e48065736c081b086da99ee171ad2da88bad8b141215eb9958af43a91bba6d0690077ef1a49ceeef7942954a1f9f8c2fa15aa2030d463a8b940b3394f0ec413e86ee6d5dff7b688d492e05a996c8bc5e99823b81f9cd19f3531273dbdce2ae5067b3d3a15ce618001ae80f71dea281320268a3a941457c2f4c4d1b8f69b11ebf27b6b0492dafeb83a8770afb038874a9b54ed2d205ed8bee5d893896dfb9814aa713731fa3bf6270126108de6fe9c28f46a30a69b881d7d10cc0336ad1a72495c89c7cb13bb50b7fd229b60921e0dbcd5ac0b8e955fefead53ee03b12ab3a4b0db9a477296c753ee200d2417797d29a7389343fc4a7f306ee11793cb080d4c925aa7e695f17be035c45289a572daaaa42480000d732eb800346362af97d2c5d772497ef5563ee9987f9405ac80605ba82ffdb7189d1abc4ad5787eb40d55bcc2511c9daea03453b0aa150e47365cfa1b02f61ad6fd03a9faf4c089bc0360e08a0e15bc29d18e298552edba11352b4810c30f67344350c0e27aff690064ec5590d5782fcdda9b17cbc6b716eaeb0710ec04d3a52b87ccbe40e006acdf6e5a630d66f7d4016ccbd8e1f1cc602210333fc45908a37856fe6f77e6d839874988c93d4800fd5ecd8836306637e0fd4a0c3703dce2d747618cbde702c9b67b0d48d397bbee38717fb4cc08350e2317106ec3d5899442e4555026e31c49a0a3040f26439ffffffffffffffffff68ddd3d6eced97494a0107a00494c34d29a594528a54ebdee4ce7c86fd0cbb98cf5c85220a6b0a840a8ec614aec1468e363ae25098902fc64ee65822d20e3854b6151263c2a95aed49b6bbb43469e2e978433985f966f32444103aef7043f9ef3289929f3f1f74b4a19063b0f38cfb39c682d0c18642b44b35255dd735944feedf7e08b7430dc5ff91316bf6c78f49c51f74a4a1187bd63ff5c734a3b7103ad0506270a0e30c253125e4e36dcc2544af3bcc50909e5a4aec8394b352a30c3aca50bebdcc21851295f163c8509239dacdea32ffe93d86c295c91046e994aa698aa118ba3e36c61cc404fd75b5d01186429435dd1da2868c2dd3460e4a07184aa57c5683921e3b843847c7174abda5937fe63105ed05ed3578a6698977a1dc72a5aca4ec678d36d25081c9a1830bc5edb0109751d479e8d842396af893fbf2af3b3a2d9474f29dcf10fd1c3ab2504c2173d431314add8646197e461ac70a0ba5cc5c265574a8c8efafc000d17105a4ffa6a97b8bd661855244cd89216f8520e8a842b14bb375279f9a9d34154ab11ac145783c8572b610e937885fa76aa55050be5a969b614714ca1663fa7faa5aee35502809617ad4b65ebfca2794ad747ed6908b4387134a392ab3e4d0a33be49b5096fb1acd907d261453d6ac88c7897c553b9650f624b664929e3b94505af56d97905eb3ea2809e5644aa65b9173b7294542314a0ecf96eea7b573472899cb66d37d9af396c1a0c308c5cca263fcd7cf31c85884924819d4526e420aa97510a1183e889010f53dc41802816158d6e2915acee65b5496951002c228664f1bb3348e4bfe8351d0a36406cfa571ea074629364bec5af38be2686797a55cc9d5be28c69c1a654246b2cebd28c62fd91b722ed598e545495706f5e91ac36976514c7f7baefe7153a6ba2807d1ab9539cc4531472bb5481b2e4aa26f2447b7165d57ba456994cd4e2ebd4c62255b143fabc389df1bd393548bb259a78b9dacae2392685108b9cb46e424cda2985793781579d9509245494b4822123d4c73522ccae14b820cda437c33c1a29c43870e6293d445ff8a52ff781e8dbf2bca3123674f93df4ff7ad286cdc6913fa643345ac28e4fd0f317a3fc70aada2a0b6b466888899095245713347cb2ff166a5938ad2c79032530491d51d5414de32e78d161af4daa728c59d560f49dfaf98a6286ddda89b91396784a52857793e2136aa228ea428e8dcbc0f9d789ac151944cf47c5367b748de8ba22436ecbfe82d14e5d27ba7746e5bf1d483a218b3e4147fee9234e43f5134395e3a889c8469e43d51ce342d39226fbcff3b518ee18426a1399c28c8b8eb52b5d1a3f64d1474744d3699d8bc194d94c3c374da70268abeeb9ac22cef56c64429432edd2c6d3ba54b94b6473f09bd6ecdd71225cfb3ae08afe94f9528beaef5b5afecfd4a89d2d866473b731265efe4a3a54d499472eb3cfd885c9f3012c5f3d22aad7cd39e834431527eda9c31dd9d087a441f26c848724a8e3062ca53230a4148751f8949a8f31951aab198bc3b9a45072da2741974fbb4e4a02ba488b27e18d7ed9335cf494479a26cdecdb62cdf20a2205e363b825fe5c81ca290636cc7fa244c89b0218ac1c342d9bb461db710252f2d93c38733d32921cadd3362cace4114644cdee7d964f53c41143fcbb3e8d0bb480a44e9c3c691a94ab6ff80280815bd273f836ee70fa50fb1b5ff3698a7cd0f85a0b55dff3f8910ea43612d536efe1a1d3f7c287cfbf5e8df9424ab3d1433940ca925470f852892e45e270fc5b8f58e61466435090fc538325769758ebcd11dca1b42dafe14214bde0e05a16f22e4dbf81de3752895663febd74c0bd1a1e8a33b954effd1d63914c469efd8f9d6d468e450caf1384ac910fb6fc7a17cfae6b4a73a4dea854339c9b49e5a557f43c926071d1e71754329638ae93da1944dd2db507a0d416a529963f6cfd950d2f9e123f3769ada7c0dc57513f22bc33c85cad55012724f5cec0419a2e569280619b663bccbf960391a4a42ad3fc62c32bd267e869226dbdd1c43b6e9c90cc59f53e9df57695b2a43215988ae293d190a765e9dca2fdf3b3f8692104dd2845c75ecbc180a327a630c51257977c250cc63a249d2959eee6028a88ed21395971e355f2824d5133944cacca7f6424967c9ede5eb9c5077a1e01a719208ed124ecc85c207539e4972caff780be5b0f5ac9f25d5e9ab8562e5c6e81a5b3567f793a7f35d64ad5828bba8bcd91d9d33d32b14e2c40bf5971b6244ad50b8ce263be909a7ffab42d93be9b83f5bcdda51a1b81ff446e579a868660a25a574d22ab78c140a7224a6afdef4592c1385c2ed874df341c3a4080a8538c14275e43ca1304269849139b683dc09a5fe984c88903c41f72614743e51a1a13b9e76261473cea52175b3847227cd71547ce874b6128aa5f1c4e496104ed249287c5f973875915030b5924388a66a847a8492eeb93f0d427c9025048c50ae944f6db31e378c1028424963ea07e13242930e024428eb7a9de6133e1bb61e4641b4e5071d542d8c826cf7bfca6fc74a3a18c50d754ac66e9bbc0f8c82683cff0ff52f8a1b43740c4a72d4185f14c46cffb675d0089e5e94356bd67e0f7951486a5fdeafefa2f06a23364f2c99b3d18596e952c69496735112274992c4f619ed322e0a9fbe448a49f916a5540be9631277a2c9b628984d909d437f5222c9b5280669daa6f7bc3f089916858d9925b3e32951139e4539282d1bda93888b902cca97273a3d8ccc86a05814ebba9476f99c2b826051fc2a95c9a5834ce9794549b76ddad09dadade38ac26a455a8f1d74ffde8a82975b6d6dd69fbcb3a26c726dad2c6d3ce8ab2884f0ee37e14ae868545170dd1f5995d95414cf4bee6d5667897a5151104d5e1e6f94eeb97b8a72dc7e31b139040dd93545d973e26651dea699dc521453c5c914d95c521454aedcbe271174c61d45a96398bc1d39a1425c51142dae94d6b34addd486a224ef3f71b4fea7bb161425911bb4f7e87c9cd0278a2e26fa749e06553a9e284b12d2df44c37dfa74a2bc1e92c4242386248d72a27c9e5644f82053a9573751ca78ba66bf3d4aaeaa8992953c6de239a685a999284fd05e16a362a2ecff2f7aa2e78ff9258a6922a4fa38f968254b14ee75346c0e19edff4a942376b43ecf1e5f3b250a5b2164e8b127519e1c9b240949a218e46a5cfd90c3e670240a4ad5a9c8935ee68144b93609691e57d9e94714c2e5defb9b8e287775c70df935a21c747cbc5f2f1951c8db1b4fc9f6a064968b289650ed6f114b459452b34c2b72fa52aa4c44e9f37ab7a814fdda23228a333327bc54cfe81c3c44e1d6fa5f53e38d7ad0100599748998ddb93ce460218a15bacaba640eca5b244431b8490f51bfce398a83289a8a4832c78782285c7e98dcc40e371906a258ea35b5a6fa0d9f21204ae284675e174f1613fc43e1459b926ded414608faa11076c36587fc1cc2a90f65ad50e222339bebcf8792e6add166be1f25bf87b2e5c49f38b77722d243419bc71c26c4dc9c4279288451edf8fa59dd3f7828c7cc25a3b9e90e458b305a4470ffc8713b14ed7e339ae4dbcdbd0e85382266984c7bf31b3a947653ce3b26cd19f51c8a79f5232e9743316445be516294693a0e45978f1a3bd2a49c150ee5d2a963ca37c6ffdf50cc596f2547c88734cd08b58ef788881b645f2885652cff51db6e78a1206312399c9cac31ee7376a30be78edf525ae5a35d03c20d2e9444bd77d0a63ce41c3b32b6d08d2d74b9a674762ca99ab171430b8810cfab27d3fc290bc49831710f2f319fa4bffa347a030be54ca3eaf3dd154a42c4bcc8ad9a15ca5a1faa1183da6e54a118bb753f414c6e50a1a0f2db1b42e4d48be4c6148aa5496d124ac7e6e07a8e3adc904251c495cc5144c275d26a196e44a15c42a4d451b6de1a8238861b50286592fde9db4f87db9e50cea0646ceb56f373957ce186134adaffa24d856a7dfadc6842418746126966936e30a174d5a321df664fcff124c38d259443b756ad7eee8612ca39caaf7d904105861b4928c8d87bed21f462b88184e28790b09b3d7df2fc8d23946377e4911841b4c67a037682117408e28611ca9a51dfdfb3b8c9b06e14a1149ecc35948a521e4b48871b4428eee78c7952bfe6321c438daacb1181619464c490f93f88ad68100161946c924d1049080f5e3a608800fa64e76c3da5616014b329ed75ed73ea6ce38508fca2789fe5b661a6436c17015f945d5e55ce3a4424c723d08b729b70d35f1d23c08bb29c485254966997392d02bb28cfcc4a947cb2a93f480d11d045b1fe47dc446e79889f5bc3043388402e1c397ae5e2df13015cdce29fb16fa44188c02d18dbde9c5d7266feda88802d0a398b7cf52a99a284522dca23eb35b3356ec2e7694192d3289b3ab7c46751b40e5b9fdae2535976cc88802ccad9343c9dccc5a2782f233a849c8c002c8af7516c3d651c1b8d9c118157d835f7f2e961716e123cde766ab65dbf48045c5190bc9ac4874f6a45313d738f3e0fa9a74aac2855083d253b4f0e39b38a529777cc3d5913c57e55e063b15957b2a928d78b300de9b64b464521868c938966652a3d45310865a33a53e893a3290a263eb54f1231a2c84a51522e31a479d4ee0f795214237c349d5736c73b8da2ac9f3583b80889a268b5c1aa430f45d9b2344848971d7d501425286127b35bfab34f944f878a8e67503b29e489925575f8a07ba4264f270a1fa9326950cd8a3027cab133af4f5c7d9d7513e5bceb1bfbd3e690a389b2f5dfabaa690ff665a2a022c89da821e669eb60a23caa52644c86e910bd4431775d6998d012654f6df910e49528da6d456e917133274e8972081394cebc3e8962292543efac772c5f12e5fb181a84ed9128074dcd9ccdeb3be290289598d7c60fddddcc230a6aa1279a8cf86f6a4714db4255ed29b99def461474d3c4c8ce25a2938c2887fef8a5cc4514d3dfdff376883175461125d350fa1e4d836f661251aa789310474f882869fb24bbb447243f1da23442b8767ca62ed31ba2b45d91743e85289968d9d97dfdfc31210a329f2d7f826e8fc14114a389bd1d6d4d22491005a94977decc7520381342b6feab064441698876de6e1272fa434156f9c651b26264dc0fe58ca3944c267242b8fb504815274ea6e94d36f95052eac38608713de7710f65b7132d6ea5db3d643d143bcd043f551eca214e6e9d84f0df12c643c94de67ca414d99b3b944b86cea1ca3fe9903eaf244f1d4a673a4810f725eed34b8782e62c7a738cedcebd7328cf5a8527edae1cca9b71528b87f8ae228d43c1c243c78f7982ed46e1507c312124a6fa0d25312187d3cd1a7c5f3714e3c4d48fc9e531576d43418bea093f496a4655d9501a95b549c5c850b25f4341a793a0536e926ebb1a8a397ca4eef43f22cd692848c81fb63d086b75d150ec506a74b75567a467287a38954126e76042966628e629d9a3fe3fe9916519ca1372d7aceecd88243214735093444cb2fd161a43b94c4eaed690139e8ba1b82544fa2419d4ba06c350f6537fbe9fb7dd2f0443415675ebd7499e24ff426944c6dff18d170abbd93fafe613b9315d28cc7aa83df972a12454fcc907cf73b36ea1284a44d0f1a5168a7ffd917aafe46c2c0be5ec1054798e1c168a490813b2637a85a25f6a966e8dd1266985824892948acc5bbd477c5f0db22532a34239d4e790fd42080f4253288bd56e881b7672f848a11082d891d3c9e7f5289427c9187f93eedf8843a1ec5a6e3286ebfcb97e423974909326896821612714c434b54663dc9b503c3fa579993af9499950d2d13759949aec49b984c2a40f1e27e69c44664a287f765a3f534a4212a92494bc4fd72ff6fadc8484424c93cbbba3e7c9e808e5382ad3e8f18d103f46287aed88ee3939d375118a49f2d585dedd106d048850124af95cc889494bc3285ce668a7ffe9f4a6c228bbcbebe64fd1486530cae99fcb237e10fb1018658b5425e3bd360715e9f845418698aa79643c0fabc6c85146ebf9c20e21b58d7dcb9bb817458e249a311f325b991927f2babd3955fae474f0a2114d32bbddec8e5d14dd3c87a90d21cefcf9840e5d14334e3695f7ab2317e88d6936efa40329e8e01774e082bcd17424657e8ac8d842ebb845a22d1e1bdfa15e2172e3c8ae830e5b14e67388e1ec74c6c9cb3930848e5a94cf3594dcadf0493a8816869cc69877db6c7b9d6d9abb933b4137f3338be26a9498e366f3111f248bf27e129dddf4b3d0118b6276d77042453279a35503160531f133c6b98e7d59c72b8ab9bba972ea4496bd3a5c510ef164fd933bb3fca615e5fd2f91e3c8fb17ad5941b6ac9814c1be3d2ebe462d84aeafd95c1b5f4571d3241753dac11a6ba8a29cc4248fb964bd88a05ea1231585f3db58cb9be0fd2f2a4abb1927f1cc3a7beda7287e30e5a9426a8af2c556a528cdd56d12bfe65f1a2245c14fc9a844eb46519d96a68779d6243bed124296a43e89a23c22c8f591bf2314e52d2fd5c1c30e8a5227fd90476f9f285dfec73ddd1dfc64f34461a247f4bdbf34f964274a66e263f8f4f426f366a8d1c189bbea4ee36653d29a7b6a7f8610b7e5e94d79878e4d10c49d1075bab4646cf5e071a006b1269a38eaad88a0df4a9d08658297affad4b0b62c9370f61184dc858992f978f0ddd4fe67327589ff2567ffcc1a1d96c82e52a4893139b2758d6889481d42262d69117454a25c5e9bc9357d1938420725ca3926a13a9cb9a4f01831c44042c7244a1b49a6123ac393d49c240a22265a32f3d9a37644a29884c9983d4634899b3a205152c226be99dbdadf48c6561a6c8c81c69b3146d9073a1e51fee4353ae6cf92e3d50e471425a9f7c7c8fb2849ec6844b93e6af977690a397934664479ed5a2b248c32a0822b3678f0b7818e45eca12639d56bbb458612932b37e215ca418722ca95125deeded39108a2aa8433130d8dd51aafd638923409a5317623a26ced499b52d3f09b443b7834c658238d31b8ae4ad07188d265c4fbcd9c4e79a686505398db281502dba83f62ce4a673b11a21c1274c7ede4e918c432496bb44ac6abf3c4c40d79f3a8a8a4431025912468d2bf0dd0306b8c61d4c831468eb701fa319279721d2bf0001bc60665e8c0a0c1aee80844f135b8e95ed1b14d66d450a30d4014c39af49ceb438a30331a6da4e163acf13b48c38c8e0d74fca19823a99724b2c90fa5d8ebce16dacc44471f0a314569f9505c4fef3946e8d41ff18e3d94f3c92ca6d3df9c909fefd04349ceb55ae59bd7d5df918742720fddb959b6466936e8c0431a1e51ebe64e443fa752dd0f2266e81d0af9c3c793e33966e246a5c30ea5f5a063af696bcda61d75387c27bd928d339275d0a13041860966d2c3935076cca174aa3225c8601dd388a1d02187e27f4c4a8d0c061e6220febff7cc664979e229788481c190c3e30b2af0f0824717dc13b149d926f32f079e223cb850543b530de77983fa881e5bf0d082d2c0230b6c786041cb2a53396bad8c8bd5b83cf14c22798cda35f0b84259d3cfe79f88b34ed50a851c6d84491f8baddb3caa50ce1141d3f40451a12c693ec49b182d35bd29146692f58ac849fe7d8e62c1430ac5cf692d841a8d47148a7e25acee268756f0804241ed7734488c1133a9c713ca5ebe2554c37959f07042e9b74bc96c59b2b51e6c42b145c6ce301d31aa3a0f26944644fee89e4bdcc62ca118ef45dc9a899450d2d89a3b7f699250bc8d94debbe9de348d84927c92e95f8277778a3d8e50126b53726635424908a5494dc6e85b393d8a50384d9dd3ae6a3ce04184831edf8eab700c03f131e7d2b954cebd2dc0210c15e008463a740826435685c6c06803c72fd217ba5c248bb7129bdbb61725a5730c727c933878514ce741fac577765108396a7fd9aaba284d180d7eba9994569b8bb2a9f967cc49cfa3dec7818b8238fd99c6fbba4549e3a9efce16e54d4a879e9cb1643aa15a943e6dd86c9ab373020e5a14ef730cd3fcd4e02bba9b45c9b4a42bf1349e2ccaed1263be451c993f6f093890b9b3c553a7f22e71c0a2904cd6e9567f0cafb79180e315252154c9a025f77d89da15e5d3ee210711a2e7a0dc56144fad936b44119fe75b1170b0a21c23627a6c844d323f52c0b18a82694b92eda3220825c221e05045b1c4a9667d1b254ac871a4a2b8f1d9438ac42c0e5494a486d1d9c3341ca728c693a7c74efa8b876ca628b56651a396bd1962689421022b4539b53ddd64e89b1cd4468a826ed00e22ef8e378a92b65c9a85a67ecc9e2c0e5114e26ae8776cc9041ca128c859bbd33d39384051b40edae7afd24f947f833ecdeea9b141e5896210694d4fccba13a5d0b2595e7af2468c878313690a1193129713fa0a706ca2f29c21284f9ab4d5d1811474f01d7068a294d157fc7b7f3c4f08195b6288713ae036aaac802313870313382e819628c6a89fd7c354899246cd3f2f75ea92744a94435e5dd1d8f1b6f33d8962be8ed0f85e133a842581d01dcd09b2d94894bc24d9bc6b87e8101285ac59d2e2a467da943ea224334bb0d17e19ea7d1c8e28fcc6f7583f214fbbd888e5347e7466949c630472563536735693229bd7a70f2f6993887c882da294b1c4272594aed586c0a18862a80832abb9c95ffb89288d7d7e493561e384d21d010722caf6aa9a515682eecf87288be88fe1e757e33bc5618872ce7963981215a224d994dcf48ea15d1d218a97e53596a74395781005cd7709bac2df35523804519220b37b8920e2084439d28e5abfdd0051d011e52641ad697811ff509ce0a9374813151470f8a194311e6d4b275e7787a30fa533b7aff0529327e0e043b14444194d991c03c71e0ae163f492fd61c3a187723e1965c39418471e0a326889e11f9259c0914c12825bbea98fe30e05d9d9f43a3fa162531c7628c9cd103dc78f4989ff5c01471dcafd13354428b5b7933a35e0a043319386d012d4ef8d1e6b0a38e65012d3f21d746ed80c3729f0010e3994cfe4d3c55b8cc9fc6aa600471cf00638e0508827573c2cdd5b5d52061c6f28e87c95d09c565428251c6e289a0e12545644f89e0f8e3614929e989a44bdad4c0e071b4a918459773ef1d1e521634b71acc130df7415f96da6cc81430d79f0fafc8dafbd91b1956ce0488349c664b8968c2d1c68289c47ac7111961965b800c719d80cb623b3bf946f86f2a4d18c24c3a8f435d90047194afa9ee4345637c9242243e1a4085f1162196e1a4349d43fa5d98510713e62c04d2cdd2ecc3e7c378edd66d3e008434989bc2459ebd3ec1c188a41262144c5bc38be5062180e7078a178bd5173dee45a7aa60b251336271efbc424b972a17c2263b6971eb750901d4cd86a66add8d43a0e2d14f4c93cf518268ce93f0bc551aeafa9f3cb24a562a120b433662a5da5d64f1c572878ce990ee623e3ac638582d4b2d5ef72cf90af423999aaba7e4c3a6962a8f0a89a9698f2e0984221a91919f245676f7738a4500e5225c63e21bbb7c41185237aaa45ba332d8bd7a4627f0f7040a11c44828c7393b473869f50489741f96c4c2325c49c5074096a3478b6fdf7ab09052b15fd9e31e34126389850909e43bd4f3e2da11c224ddc3053e250423987741d722879752d8e24149212aaefe7314868547dd0133eee2314931e37f364fa3a723642216586fe20b73ad4af388a50ec1cc66af3b35a5f868308e53989fe7baaac4c88dc184631839e519abfbf7356710a37845188a974cf9a860a1531c128db96acb27d9d2fa54368bc0a6e00a3d4721e9ffd4bfd07911bbf28f5c6081ef4b68650fa0bdcf0453979d0dcd3d350a65e9425bce8ccb7ace8cf8b52e74eefc174d22e0ac23ee6a0ee41a68b424ced398da033b37e2e0a3a2d463b4352c91d17a5ca74daea47a7b4cdb945496ab69a9c7d4e9be7b6287a6eeeac63a5dad4a616c5acdc3c1bbb6951483275eaa0c9d486cd6fcca234775ae2368409a665591426c947f975bd7b7363519074a54c6bae4fd781453169ed1cb74ea8b5385f51d8205ce28d2e35d2465714731435a544e738fa6945d16cf3a585cce872b3e2186fd4487af356515c116ba3c2a48a422eb3d4efd2b671572a3a21e142dad7858a624f5ceb3aa1ffc3e814e58f70c2c4ecc414a5fb70ea8389d7cc7679e146290aea7f669d9b365f45a428cce6883326836cb66714a5b6897972892039ff2e8a82708db921323a44be43b1c70e59a9a50745d9e34679a7dafab13f51ce253bfc52d2881fd313a5f1c8f93e9c30099eeb4479be5227648927b244cc38515ef5c9a3426f44df7d13c5709d12c36d242d9d9d0ea2704313e5798faab339b991899248dbc9e9e42fc20d4c94a3969e95c4dca971e31265d1f5b36dd9b2fdd912450f26e759dadfa844692b4492d31f53c60629513ea1b1ba7bfdc13d796312c55d7bd13f26ffbfaf23dc904471efb3e8bdd2d5ee2a156e44a2a07f6caf242471ea2748e023fa99abdaf06e38a2a46183868ae65c234aa1846be81699bbc18894f69db990a6faf28d45701e6a275f21dfd35ed54e965ef986224a8c44941876831b8828c785879c0f9ba64575e310254daf29ac6f4b686acd10c51c52bcfb568da58cb810a6068ff712a2a875e7b5217a1d4449948ceb3f273c7dce53100521527b437573732e0c44c93d7ad5e6c60f1f4b7703109aaacf7fe30f2506e2e0861fcae971a55746dd87a2c9f44d6f69be39c8eb0337f8b046b8b18792045572629639e149ee6998618331d028830c36cad8811e523237c9d40d9e79f464e468438dcd43e93aede659d1731833c6683c943fe57c4fb6576d63947195a34370e30e6cd98911f1398937ecc0577858b5a6565b8d7d6585ce4fb72e6974d0e8410e5275286ea9f8b4a9a195234287e298ea3f493f1efb437328e418fe9b1ccab6b964460f1e6763c72c0e2529bad6456efb88689bc1a13437aa73c7999c413d666f28fa68896da5b953c868e68682a9cdeebcbb897964ccda50fceecde82933ba556dc686929c9cd91aca31e4ac6e4f93a9a1fc1f4f288f27374b43419e6f9c244fb3c4d46ea0a19827cee9e7cce919ca13e45544c722cc46cfcc50acda8cfde2316f67c96e94a1983e8d4ca339758ad0da0d3294c389e6fb9c105472888da17c6283e74ebf9fdd1043715427ea8c58175d8b8c1b6128e498f24f9d68a481c61839cac82276821b60288bd4036cf0e02b80860dcac8818009e4a8c09be1868d36ce58c0040490e3d768830df3698c055000001f5715a8aac019b107f4d1dafe5e8f07103170c528071c3672b4b1460102d003af800200c0468e3652408000bc0d4c1b3640c398810001e458e34f1b288d35d44800097294a186514000da0080000000a1f16ce4f81d5400030be0000072e47031082000c3864162142000860d83d258438c030060000d48401a6a8c818363d8c831c6180b0040001870802b830d1d9481da18638c044820093d5003090790c011d438e39cf16718400246b832d858838c1c668c3146022450841b630c044880086c7c8e33fe0c037c0ca31872b01ddda5f487241546f9f7439f48224c9c8f05a3b4693532564c7650b233fe8c3de3cf0046e1377b5052eb811ae78c3f43cff833f2e31757061b669091c38c3146023e7c7165b081d238a38d31c648c0472f8a9ab34f4ca23f23ce2363d9aa0dab337e8d317450061a0395715e8d1c5d6ca051c6e540a30c36cc28a30d1f638d37a38ce2453128eddb318a67a8d211dcc00946d0d181147458e26317c69f9841578cd4b02ecaa6b494a5e610c6e40ec1472e78172f2fafb5d74eabb3f2972443e726b963e2a2b4616467da399911823e6e51f6c9b32525870c348c192778944609d816c5121db36869994ff7d7a28b7955af3bc961631ed3a29833dd49f37c612af62cca1b24077dda22d88e289541468ecea1461a63900f5994efd2d74568927e2d2afd884516176b5722afeffbdde3213a58143c795755ee5a9a675f51ccf3b5516eb6d695b9a2983efe49f6edc6994ac6d618080d6386b123c2472b8af174c65bf3b9eac7c0624539c8cc6cf52082dcbe3e565188b9640c16e39b3d44398c1975830f551494f2bdd538ff4845f1c5c773f21da981e775d01fa82889ac1fe7455bdbf4e84cf0718a62a8d8dc5d6fe618363bdf820f5394bd4f899c2072646c8e5294ab675486904da4cb467a33f083146551539b4d6f928f519473d27d5635f9d5639ea2e02cb28964bae15be32314a5c89921537f5216280a3eeaaaba1e33940e6123c7186994400c31de0cf732aa3e3ed19a85bac8d85b6b6b99d79c0e4ff93879d5c1631967a481460fcc48e304ff1d60f344f1c7d55e5e44e6ac3f3a5194d87d523d74be36ffe044b1d327c4f02673305d7d6ca2ac65123b7ba51f9a28879fe7db7c42992854d54526044992236b576deed01bba57f48189c2be2899e7645077f18a21860f3e2e5138a5c397c7ddce0d131c9c34cc604be041c8646a9eddaec1861a9530d5f5694eda84849950a2a4f2f79374cb842c419328c868fa8c04d33907591225ed48717ae39829cf22619c207c247c08a15f32b63e20d1b876a7e496d85d9d564bfc9d901b46c35a8d33cec7234adb279310f921e786b0e1c31105d3ba31c77f9124845023ec0c21d3a9c91c4694d4dd640ff1219cf6f563118d4d3297d312fb3e14510c3246489b9912ff438940442292da6ac32b6d4b8ff5ffbd8bae107f1ca2fc2753c7641d43143b4bb4d53d717a1e2b4431b47ba368fd48699e0f4294a4cc060df7a9053e0651ce6c9a66af564b659020ca9bf39921e7d31a9f1f8128c765ca09f771669b06c4c71f0a623d94fae8f9d3c522632b071b3c7834728d6703046f83359e04676b3c1b65c8e0c30fc5d0216f852991333efac007bd7773b469c8d8aa3d2867f51bd207b95a42157ce8e1230f25ff1a7b91928312268207336ec4ec26898d8a786a2aa563d4901c84c8c71dca51c29a86116f3139fc6187a2ea6d6bf68cab92e0471d6ad55a512f4f5b395da6218ed894a10f3a3422a93a3dae2d6564c2f8e65e73db0dad081f7328c43c217bf698ff904321a9c412951e44d01cff8843e94aed830a19ef83287dc0c150d58de52963c918f38693a6aed0968b587babe1c30de51c944c6a1f4b7af4b90de5a4574d49562da53e3694ac4359a7d0d512a4670da5afd2412999c34612b61ad06b21b369289abed788fad9bfe93ed0f07186f2462aa14aa699ade43ecc808c89f1abbd759a3258dfaad9766676d25d92b2f598de7f90a1142a3dbbc45f7b0e570a1f632827f1e1e488a473ae4a0cc513a9c49d28dd47180aca22fc474a157d1ffd0043c1456ee88728faf84279bb7cee23e731a57d32b6d028e38c03c187178a953ff236728c813e051f5d30c9e8985482cd5d646c3d0e0c1b639861cc8c0f2e9422beaca989570b3eb650ce5affea611992ced142e136a34d921d42a39e4af09185f26a45dc1c6212195b58592cf8c042292fb2e94e7ae6ba5d64461a1fa0c3c715f8081f56b84fced429a1f05105b7bf8403b30231c4c8f1418582eed0acebda1054842ff89842414b628841eb35ed8d52e023adcc668dc997c647140aeabc376c27af091f502875fc8961840ee7aaa3271483483a6fe891f97042612bcdc36a849a50180dd3900da1fb44fec184b2e479bfcda59eb191fb5842c9d55c9350993f9450d226ae1fb1c44b927e120ab6ba39e536089d4f0809e548b761c4feb68a6c3f8e5050dbb6993ded9ad8e4c3088c66cbb04f6d7e14a1a0f523368896edd92602fdc407114aff2fbaa744b4c4581a46795bb5652cdfc4e4d9431805d93a1f53deb36cc8c128c7b4a69b7a3ee4371518854fc276ed639c93d4fda26cb9eb7daf79f8a26c13fc2509ff7b51cc1c4cf7e0456945627617e5d249e910c44d8738395d946283f639dd3517c5338d410f5c143e95a69a48ced324bf45713b8a8889253d9e981eb62806a5a324e1aa7ba34c1eb5280819227f0ca225c8b0072d4ae119723627e9dee979cca2301357cfafaf43c4d44316c530d95427fdb24194dc231665dd51b133e29d1a7758948466b72ad1f21585682392ee0d56bec1f3704539cbe6f491971b7c3766ad284c8c5632ab88a0d46f66ac280617d39dab378e8e6c15c54f9bfec48aad8ab24419db9b7f5351cebca123b5326f574545418968d9c136b7e6bd4e511e5396412c440f53381a43d2b759bdd941a5c84ecdee3c4554c37990497b6e73abc9d8da8107294a9aed45b6388aa2276bd75ad1431465917d61265a92b135865dc70a46d061c782ab367884a2a0e7ebed3555e9a022288ab92bcd3387d63e51d8d1f5214a10f144d963923b19f3dd89f2d6aaf5e69fc6e42f27ca793c7604d3e8260a5123ab281511ed9cd24499b6e1a4954a3c3251f4bcd2161af355b685e78189828e49261193d07eb23c9728c94f3192d227478e901cbd050f4b1443bafbac91ffdd6fa6c1a312c53631d9124ce4e62494286a943ba521b3314acc4da29046ee6bc367f7a49d8724d2d0736d76d946a2783d9aca4a072142a29c2e36a64e4abaa90f1e3ee25d13919eaa537c506a20031e8e2809eb09fa4c7754174f1a8f469494e40f153ac830a2a0737f28eb95902ee6333c16b12ef05004aec0231188f038c4b22e31b33c535286289cb80f15273fa8896b218a7b327cd2e499e447888cad5f6351e04188d2a4fdd5fcbca32e52973c0651ea2fd1e7deba3a121744f93c42f8d872200aea46cae8ba1c1d3d0344b964ccf91f4aba1a6d3e78f8a1d8373a8fad6cb995f43cfa50ce392893d9ea1ec284cdf850c84168b68a51923699720fe5ec22742e6d3e79d3f5d043a55deb33db4ffae41182471e8ad6ddfb396709e5ad93b115030f3cf0dd216dc9a665261662d9a1782f7a5bc3485b931c8f3a9493d050b2e4860ea5ed1c244e4c2268f46c0ea748ca1ce1d400c8c1430ec591ff6cdb2e71061e7128897bb816d712f14c8be8018762ba13e1b3651ebf51f4784331f665a4bee98a921c0f3794e4892472e977108fe0d186724cb2ce50b5fe1a71f4604359f44cba58a4d750feccd94fdb720f35144ef6ec94568f471aca9b4b359b2ccd9c51f64043b16eec3aa8c65133efa1c719ca1e73278f6fcfa896d1c30ca590dbfd575122083cca50bed8b89517094a5c460ed68007194a7274c964e9e135d4308f311494b687a89e27e524a9218b50a824ae0622692c128845e28030180ec469a78701f31308000038228c8682c168a06992f201140003492218362a2822241c10141c1e1a22120b83c17028100a85c1803020140c0402e1b04defac0f40a9a01f865815f2df8f9132ec9a4e7c45842a9d367cafaeb3c90bdb573869bec39f0e0271ac5e95a4becd9a1767d3c1fcffd1fcfb368002da7d0154f37368894c17e1e0d38571a8785c2883e7fa066ccd2f13f20f5f18fc82babf04bb9ffe573d58f69e3526f7b118ac8f26f3c751aa5d099966a32c3258d7ad2fc8da566035aa7f981f28f46542d8ef62c910b509356ec5ac4a81dcd64d5f19b43fa2a8929c944e31e7ee4b771ba6dc12c673c7883022a94ca02fa034dce32e76d84dedc34ffc92b4fa439c81a62f830cce980a02ea56697b7bdc335288fbdc823b395c3042d5c31ff105115dbb096a4594c46dd298ad2177184c4839d0f5afa5f83681f7f581e83cd917da019c01adf5208384321c164aa32a6f4ab7e801fe667eacb6f6a6b4a02b565079055ce46a99d074e168bca04456c384d19aef20a5ccf570eb03e756132c37b7a725d6660c51299495448a3bfa112743def697da199f44dfa11911e230d52b5fc7367d58a4a96140f711cf2d9bf61f05153528ed3d253b50da219e91881ad6aec3e2bb936529227d1c6d580ddb0c0f1c284889f1d8364a77f6143993d5f5e0659d533a90c8e9cb5d8d9a0543d8669a145966ff4a2c584fa1e1d648e8da520d8e7dd76d9c8d1a89000e0db7f5c8418441bf941ace3ebacb88e8e825e3b0c4740b19b3a137ccc9e08dd4e7222fad61fd3fec18524c44d00500883fd1ba044a982be7ee4734e3693761e6ae8b515cba146fd87225bdead0c2bb54f9ec256478ae2973eeab34a660df0feb2ec0614d57911c1ad4cd125b127f40f27d7f5b6ca5ac540d886b227050e64bc50a006dce22d8dee6f64c50333f33da7068499c12fbe0ac5d76d339689f50dde3c674ebcab985e90304b2d194a0c682f9140cfbca6a0bfa5432f96cc7fb66cd7bfaa84b1ba13d086f20e7bc8416bd01c342b3a4b184cfb93ff85a79b8135640d3c1a5907e259836b63434d21d36115c33e6e10df209dd1fe30520f664b65d0c0918e01370e19da3a72add89653b28663860ca2ce7cbe72af59e460ba1d1a244120f5e977290cd8522a98a2a401b170a12bfeb0de0bbea128a53cc03da8c191569ccfd82562b8c2a6a52fff20c7b0aa604a4f9f195f17a5d3fad581e0cafcfef43bdebb0ae6792325cf9c46c6820cad8ec9a4bda07a94c5e9c6637a2edd891543cede49d9de3a75c6306d8e57176d4d4c346d964524865f33069c22375e1735709030f4a80362cd5b2849b29cea9d3a9730b9167795cf17da83271358618cdfca307829d3a321ce42119b3aa2be752fefdae278f180778ca66349751aef60382fb714737fe2fba9884c945876f675e516d7a157cee9093e8213c057495e16fd49582a373de85f01af3c7ae779f96d94a9e8c4886ac2ed8efeac17f43a5497449b4d790b806a18241e9143ec1fa5f232bd0dc4da423eb821a9849a66691a5a0115a2b90019b57d11bf49036ff9935cdf9e7ecadd9613296ace0f43b077f3240cc010aace39ceb4a2c97e8c84111563b6e79e32ae98415e76a5d37178e673cb0b75633ac33b0061379803004b104eb55c8c5a7ef2de8324a9070dcfe73b5b21544a6c51e11e2c46f115b92e31063ddc2ac9ac79f21ae64aac664929e0e6449423d6da371fdd29960a221b63185fadb7c662965eef9ed0c560844006dffc9866b29a4c4b65240974e0e8181aa1e1e1090ef576e2a7d92c62e6958ab308d9b158245f1b27d03f17b68fbba16bf3f5c6df79387ce4c8dfc7157e6e0ae93450e15b813f78b09bbf947fde627120f1b200363536dd96d402e32323c8dfd1c09bd9ecff99b4699f7996e40063d47956fcce4c95a0494ee22b1f6a5894165eae66052d7bed3caabf2c879bddec2298b4f4dfdf8efd8949a7047450cbf651cfe5e6e051c5dbd1d00c3b1699a48e0bd9cd80f1d000a172a8737412bcb2a4acadf2700b2f3df64479b3dbd333e4916600649fcb91c477720968733047c39f1a273aedc71599014bad6296bb50754bc895a1a459d942924664a99291f7c24ddf83fd2ee93adb54412bc06aa2216d011e6776ef52ce5d49540cfc736410600893ecba510979a74c32a5c01f62a1e0ca5785eb78947f5561f87d964892a64966b9f8d2151bfb1be19a11826fef43d196d8781207631bf9ef7ba6d5ac011f16346e47ef770ee1eee002c477c9f79caa4aad7ee225e0756c7f3d0eb05ca56bf86232ee6cb261eaaeb853b1501ef4199f5ca12a3e5be5d0b3937e740a6e440b3d8eeae9877c41a5fadb672556b5a8ffb85ca9c4dad2667a23274ed0754f59b3fe93fd200e71cf9062e191e9d6544944ecd5762f932376ab350edae1d767e7bc4ac43bc10f20bf751f85ebcd03a471dde9b47a7acf611411a9cbe29a9e739af560656e0cc81c0d9b575509253044eeb39a0b78e7ed75d91b813f12721961cd5fa910c57a8645d40f505eb8ee328caafc0822f3b17812d290de611549beab9408481f2176221a4a7485653107fbe5964f97b37c49799946c393370558ff1511678fa3d545ee604b4102a2ff9d2fa7d67a241bba2f50168e19d5ae33435b4c2489c71fa66f9fb4fe7f131eec05591fdedce8c4f0a84717ac6506babed27c915ccb5cfa331e7f5605deadf2372aa60f0fd5e9e7be2d93c035f83f4d636e15820397009f36cf7faa42dd62afa48c0d57e8fbde5ac20429fc1717dec1a3242ed76d495c22f643526f6c387e325dc59e8b8acc478a068b8a315a756d98bd371135921ddd870c29d44c53289a5952cf4943f97191c047c0d891690da29501ec4aded5879c32841191388a4544185cd6c87453de28152709a0cbd659ae17e2802519ca35efeb0b08addbc3a1dcd8c97f96de1b4b1a1433477704630a3b264fb177af952f43ebad4ece29339cdde9f316fd21d736f3f2e13c017cd23948bac849d87100f62617364b75486c0f259450ead543dc3f4bfc2f2480c1e1950e73b889597c21d52a4ddf0e2923841785762cec61d20423ba476812e2c3d0eac00c51250d0c1e81b11d010c4c8a07d7532459900f602b0a24b0e74ad91c5beeb94c3ac3f98b79b818d8689f9dc735963fa1b2efa5e1b2bd327420c55b589c1f6c440d9b5bf8850214211b3966286c6b67721a77e02b220b43c6c0b65b926a2b41ddf53bca345559f926a751bf03895cbafcc1e23cc7d3b59634ee35ef5e0e30e09c1002f92444eb7dbd202ae6425cd3e67fc51118e54a2f7072008a162983a1dd804085ab6870b626d121a81553003708e6db86146a1cf7d4d3b7719e733a1d71d2ae82f15b32fede9a96f6f85d887e7362afaedd4fb2cd74283bbf4cb9b80624717d146f69de93cbd02dcaa56448b516f04455e079e04ba24d0358a0d1747e25d0cf8c5f57e2efa98e2f1be52af0e7dbc48b813f082b1140fa131faf0d0c34cc89026f504cdd619c4946cbbabc400983d73aff9f35ca296423df2d34f415bc7e6365abb8519f1f6c6c9f7b018eb2612abd827c96d1c14e27ef0e0422b6bf3823b66f68c10139a7692366522a999f9ecee27cec29be7a0358fbf6671dd9498cbf6c50f7a00b696447c51293f8012d24cfcb5b49937ff9a61f169c837b68848404b47575957bdf3c12373ce7035eaf15817490b2b23fbe8bfd6f865a9d70adf9372a3bcb3205a311f1868a974144059d936d374cb14d2b2024bc1fbe88cf48cf023d0def31c6abf80614d601b635c379d9f8739700e1e8b2796ce2e023f790cfca2ff0c742cae0f24a47d31782cb229fb98fd0a3e4106ec9d0f711c8f8aea7226c259792e8201da1aede1e909b63ee7fda0a4a71f0b36556d3a35431893bb48f3e3dee81db5256133d6d205eac2a799d94c6a4763fb7434512d899cfb479c2e318b947ee81fade83481fc504c0058c9d2593d1362f28ffe0d27705272236316735bc3f930b4d9a99b5e23ddd52c61f6917ed347ecb59b1910b39f4cbc22797c12a3fb572314dca1670d8d701d653ca4278d8a27e59bc2aff298882aeced3c9508f402f7a9ee9b77b3c0eb9ba2a321679234453429a567f6a1b7a0de0dd0487712a53f1b499a756a6849a5c1d7fd8c2c2e8e1888e0ddfaf2a2b6cf8d2fb856db04eb4e4acc40e4d87aeebf8e08ddbf2445c69d79b04ae2d4c0eb25ec1ddb8c9fe90616aaace858acaf8686adadbc81e0f3ac2314e4bbf5fdbb97d0da4d8b4eda5f951dd553bad64a19f754004c49f8a074b0f90d9d9a3451d11c4cfbfa5b0084edda65aaeae2cb5010904ab0f46182642a6c421b558f6ddc63c6e902cee0caf72bf2a503e286311e1cdc8e21ee2e685ad8fab9185857dad90e3ed9465db49b3bc5689a0dbe0bf801be58e0f3e8f7299e1572499f1a06ee2f3384851e9e6ec999dbab705882a1883e18fe05c56b992a6d8a220fa7144b4cd620a77cc03f201a28fac0cb4c8fd5790e9921d32bc4c3e1cf662965d81bd0d47e5852c9d27452a208057979a28dd358b9f8c6c2c64aa5f196608782286086fc638e5c1a255085bff3b971b93ce87f3a3e9ec85c2f6efe1e1659b20f8c32bd8e53a94cb1e97ea6a6484e7117411b5465cceaba0033d1bde642216db596e89889be57c2b1cefa6aa74f23ba04c2dc2c1683806bb6b8c140a52151cfdb98c965b2808cbacba3d9f9248b684ea4740bd9ec24792527998d31c61f84072dd846f3be0e895baa3f89506d6edb19038b726059c136de7bbaa11808e8589962131aee81674b47a6e309d396c0aa7a4256896c76e14947e5cdbd81473fa5a907d2a49252fdb210193285b4e9a17b7c4565aec10a2c3b0a143763eb8a6cdec235448fd9ddcaa716ac794dac9ac540512768553d9514fd3b3c24a53579014166b7bf265bb7e13f79453b71f4339ecd48e8685b9146ca24a21380e7655102dcb8147f60b483ce8914354964df71213fb4495269a432ec2f73242de2f7f245335814d23e8012b568700d46728d000161a8d6c80a3670d18ae3d7c2a98124e4628b1a36346a228fa7c9dd43404a2ed15c4038902649229b2e5f1772ce96a3cf141c153cd184043d7e7fcd4d17133e67af4c631841699e1d0dff123252d67f6586b386b307cf842a0e125a00f9063a9abe0e9d201440974fc4cfcf12829e444f2027b1c5735de713cb8af5696b40949b5e265319496f61039abc62411d0b5112d0946bc4e324ffe423739a969f3a83808da538f4ffd05d8c37d747b104d80dcd508bff88575db324bb96752da9c13961710abaef7ae5e29e76cab698b358d61e2fd995886984d40208a2ad3d69553eae2e9aab1989a32751b9d4ec645d89407d801b41aa1148054684e89264775564b996071964870a52c4ab2d8d64158ebd3fc948b0770d2b3e3c1d24051e39d9a8ce88df9dad46b5228a3eb1a5d4d16b2bcda1e9daab28797e12280306f8c8544f961651dd8e80260324df1fa68a6862af3fed9014538d3e28e5e1f039285899f396143076a46112e6cb0b89028638efb8e3a3c9062570c355f56e35dc6f2d4e98468b067964fc9a11e182b1796da16a41287a70ee27c1ba54002e4c0a792861b1ddbdaf61ce40d8c340512ff4a642c29e157c03077d8ddc8da54c46a28ce44a923732c44b32377ab3b1123de07c0638840871c7cdac663521f43d420484c8d14f7c972985271bc59712f17c2fbd401e242ee0c7095021328365c707e8ecffc630843d8160d71b934e6503acdd4cd08ced11b5dcf2d08137d1c0113d36b3636aceb8ffcde8d1f44d519691385f33b282750771da65788ce759d31ca158d85920f1e33af5232b90ede49caef50bca28430ee310a46ea81a6d58cba45c62a10cc8cca181ded9093db489f5e0f8d626c74a10b499b79092f58a2be97d76de018e1d961d64ebd2348dd396762c3f99c7a9cfa76420f71b79ee8a1e4dfbe7558bb86180593c525df26213b739a919d99d19266eb2fc6a4fe4a88a1d8dabe19b82fd6c2043d493db5d911fdb6ee06305a1c8141bc601a04258ae498477d130550b3510dbfcdb1408a52a9bc9876a25cd47bb4a50c0338cb91d9d5496fbe23b5dba137d21373586024ec55e6089bb0d1e7c772fb564f25d65cfb59b6d1761aeb47e67057890ae5838a2d82b4e80c20cee515065e0a5c0ff014a9e569772e68f58899a114420971967964382aeba7c58d7ecf33e4d8d894b3d326d3718284381cf29e0ff6b5e7370ea3e1e5c02584cffd6098a427c1bc406f4b87436845017cabbdb4d6038b89fc1984fc0cd95d98afeb46c817471b62cdb7857890c2968e34de6f377bac138fbae7adafd434d67dc792bc0f15914f12e4c3b2bd98569b486094a07a51a453ac8e0bbd2556cfc707a57c5f9eac500e16e1f89343e2bd3ce98042df87feea31fe3053b4307b4478d6dd30074366af05fa1e1212256bbb049e5c72115f9b7a8d5d5b052b4cddf5aa02fd6f8b7eac483452ad0b717c3d2ae683b313421ba5c767fafd53570a325c4020141f81065ef61268dda35e727a3a284506e15ad958270cb42cc4e326f61301a2a82b5696462e62169d58296a4c088bf1465a48a268201e791a88082dc75fd1f10bd4905eb0a8d77127bdd88070d6c69ca8df2470d7e04d2e37a047b4345a93139efd0f72d66c64d5f7985d90a62bd406ff778b0d1d6a330011dfeb55a14dc0aa2a51205638a7c8599e913ef4afc6986c40923201a5def91295c94985e3eb77d64311c97318a84483118ce2090a9dac25843c08529965632e0302634ab7906e46d2db4002197154c95c7d97946048fa887b7ef2ae3e14e9dec7a2552a57ad95a3fa8d773b192262e6136a3df5aa3bf8fb2b42a9d815fbd42a47e6b443a88d7512ad984a7975000533f7efca0dea7316afdc9c9ecd74183fb3a1966c5c478dc4907bd441790fbebea82159013780508cf5d523972882903dd7a893cb8e77492c64c89d9e7858637384b3c8c2dba298a77441c13b0d1d73f46fb72f32f3adf1766782c9c424fa990f93129642e4fc9ff25e04185459bfa12f0197d346bd079f3516d5ccfa37df9523edbc350c0b93835cc24275b7d8d704e244594bd3ce6d2af58211c4e0f25aeb7a669af938e7cbce395a8868d771fe91d41e1d144123d0f149511582e141c10501bb7a694d95b81c655338934f1a5febca2aae60d96707334134a0f051fb06b5b2c22b23096199a4606be514cce6a1e82e4d589aa4f876d7b7ceb3b386684c9fbbbe1995973f7ab54389a087c1617a9443d144dff1bda6c2e9f6db64e9ca672523fba4745a54d88c854bc256935e5e0ece66bde507a65862fd3bf9059458268492ada6545cdf53c5e84c169784b21a01a4b2273d79036d44b13ed5a1e8a439625b3245e4be58a5919bfde7f5aeb76a012ea9fac83cb08d1bf444fee572a39127d3306630437b986d594581278e34fbc427fff2dd3069d294a58b4b86e10fbf6d6dca43c13399247ea6708b54bb48799533ccfce2ef1b46d08dce79cf127c23d06b2e5c41cc6378d2b187afc36de1bd0fa8b9022da88b732ca39a118c0609ad68ce0315f544fe633e802368a28bb7a5c31937af3669174b23a16ac092034ae052db9c4c3894f08929d9d5a4f951a2d3c042b6dd11f550e5e726fb806d7a61e99528c158a58e32d834a181def489bc37f00e2733d547fc08cda12051006c77b0a2627b0a55b93ddc9003eaca6296e57a3b7d74bfd2308d835637187969c87d1a22969e41ed629a114091e0dd82397c0c0f0098f1a411252e70871c2a3ba274c0df305e43b21af1e6cadc78ca0df3eb32e6cae0b1d113cc50efc3d53c65125f14e1b475918c65dd4f8e271a47c5d3c2cb452b8ae5d508cd80fcb6820b18419115c774233400f8fe410fbfdcdd7c62d16bb91264601cff45fadaeb0a49e9697b75f5c9bc2a7a3019415eda76e67d712079bfce8189f918db213de05504d2b08bb6a7e9f4643f12f4ef66201393019c79df380d3ab0eff405d3ab8ec7aa5229976b4c912f2ace265a86c8bfa4ba7093ac9bd3c5d2bd04b4a1700b0b3a19c1b07d54a8566fd4fd5ef5e18c8d808cfe93d29393bf476d1ed5400462d1f0ef34276b85b1908a1c7c7b403bccbb9a5702e8976e8f2228f235b7b14002abc1b0eeb9e8ed9e0675e87ef4b9073291af44ecc8387b86d6cc5acd2981a7640adaeffe5144c94e8ffd33669fa90675f71785074cf1c0075a155e660d0c2d83ee9296d4657906e46c805ee54551b6ab5a6985e833ef3fd82a143505311125de4a885ae64926fdea61cd99d4b242ae26ba063e6a71cca9da6d2d95189e3bf885fe01a4ce13c19949f91786c0138e07932c25f7213a4e847e4f02b7a13caebdb7f133b5a67618ae10a4c87114184cd198c7035affcb08ffa78bffb6c7ff9639ff5af4cf1ce0dd6485824b1d282b6aa4e11a58cb1c67a8ab5f206101ff10ff0d22ee7952e311fb6136db8e53430f2a3c2a6850988af50173cdf47d2be89e44c9d6c935e2a65f705108be889fa63ae6b093f8ecbbbe40bb70824ecd8d51144287451542a82819b5d8677e7b9b48fd43bc2e6cf3031e1894b01b3744901d4cade23321ff92dcdd33beb337922f3239fd272ba133533d22dc15fc421679db49c2f5d640fc2be813dacbc48c3f613d1ceb0534047f7fd7728ac87754debb37a75ee2dc09d53f3d7d37a99f9f22e448bea8ad7bbc096ae6e977f3d2f77bd2b33bb2ee7f23665bd09c776cf9d9178e4bc08b7d44cbf9c68ee5129fcc4ebc4fb0aa2765d1afa610a932229ced27487ecd542c58ea27310e5e9ad47ff6705117d62ea21d891cc80c5c044b62117100ac5f0044e8d2953e3047aed3eab19914ef0b831e9a7bb35529548b26b01cb4540aecccadb429a7b0efe1f429eba0c80927de754759cd0c4d67f24709339c8d37ba12d5c2b5f347f2158810a81cb7727046a16782c5c34d7db2135077552ac948130974cd417428c13e9c5c3aeca02c970c159d45817605f6cd8b13bfa644b0f0708e440c316dfb6ce9904e365dab8f0e39c5fa1e69515684d2f8b7d4017ebb22e5a46ceadcc26e75e01020d15652da53ca490106d751ea68e0879bbbd43911b9c8eefadcaea005a72f6de39ed768a651b2b69b2f74d02ba2c945defe38b5ab8d3acefb474f50bbd9da4da712622102db1518f152434720f74919e8ed000b56c34a7aa3523fb3b31070b6d8e8e91ebace4ccc639d79e8e31a89a55c8d8ec9138fc19b8813f654f91e97234c23533656c09a97e2b8df4d21ce20e61aaf051965f417da0cd709155d424eb0bd93f2d6f9eb3abc611f738a58db55a9b5bb2ef731cb564bdf7f15613c2ca913c28349e8f1fb9bb4ee6dd20d903cebd02b9b8530dd908282e7155fcb30b9f0f8aee2ba56be3ea2f51f99bf0ef5ab871c140af2cd5b3041d29d4d0c6919b348b8cfe69e1b9a0dbd23887da0752ba1482ca7e232fe79d74e05f97f12c9742dee722369091142b2dfadd49c4c31669992edebd3db8e991c330ebd23db4cdfce2b551cdca08d061d97ace56c0d2fae80eb6f27b8222717d10d1ab8495fbc66b22cde1740a7a0bbe0fd1b449ac39abbe21d226d38670c50b01bac44490c9af9cef697214dceb1110f3a14579cb62430446a53688cf0c7f0009f7ed7598bc9cf4bdff54fe98682aeb2866124b6b33a22d37ee9a82808c0c5ff9c6d5361f917c8c20e338849b2ae32fe4692f012ff319a50fc9092fbbd252ac32804a08490cbf5fa76a7bea9edc1c14f20b12c57cce0a641d8dcc50670ff87c227b031232c266b0cb36684a0667bc1e1c667ade5a7e089ede81c7450d7f6752813b35366c516e299c742bc01d5c181609946af531191611af0f1bd54ae4d67b56fdd64adf15f06b082f310cbe366ea3575a7da1dc838511ea950a9cbfeef9f5ef2738b57e4c1f6f0bab3bd5e62349240b63381dd10ec6fa5fc8de2abb8a4a58660a7f28ece32c136698a7553d074c4e7d164681e63215f9cb75c5fb561f5b3b114ef965636d90f4e4441b7fd087d6d98cbd9f5c1e226862114353234e7aa9bea849fa448cfbd02e9e81b152fc63191874bbe0fe20c48e1cea50826d43afa2a72a96e9f24fa97a9d69925d554e663adf65a9fa24c4a2b1b67bc7e4b76421cd5ffe9e20105dbb302f20982c0957814506053b543233a2c7590d0480cdb0b3438e0b548520f0fc2711c22a1f767d354d8c38b425774275df6de4b407b91ff8b30dccc9c6db9b851fc660e1e21d0935836b4728cfe45f51c3da2c39898de35f0e5b4ecd0e0c24f98d9d6bf7a846d078e2e7c2ef0fd14d0e31bbb6e4554d6d9853caedf08a224240acdcc67a8c28a0062623befadc038060588ecf6de5af498784f5e0f8fd2bc5f6e6baef5831ffd27ab1dc35f09b0159ba74bc834c30c88e8ebe0476b38535e78cd6837a87471d1e9ac0b684b70a04c7f69ccb0acfd4ddb2a7e2613cfbb959fa87bd16461c6aadb01af448f8527b0bbbc6395a084aa6c1bd3c775d413dbfe95713d162017846aa7f05493869d34f0114c24833b46daea03705317963e662df4824ad7c4c6b46ecffaac0415f496639517c09261cc67fd5e098c341a8c2225ad411379a938cc0098fc30661e1bda1459c50036112b21a4a90afd0dc7ba085678d9c3118b0fa1197105672e8579866656240a2fc3a12ae3ba44ea1fe87a40c22d632402afd2eae70127748badf13f7b2d2c57856ee6e8389910e639806ed00e4341a76687c65207a6795855a6e13303c94c4bdf8caaf77404b3b8ad6c2d3e9a8a6789ae5393b1dd2b86fc7187f6aa6d6482078a4ccbbd09590f1cfed30e4529c12db2cd681ae2c748bb956132becabf5bd1edfb559ff8cf3947c23e61a11cf4ca7e82852ad4f1769993e5e6687e453125d5c2773da4f0fe6d9bd881f349fef305fde85b8d855bebe0b71b9eb7c790b07c92c428bdc9301a73b6dd6510f2ba408bfc0f3da9d1cac75ea1df87e923fe39c7dd623e339255f232e430cf601ffe425ba6b382b53510ac1ae9337dd25030dc118c89809e079278f779d2fef725ce94a5ffce5f304d17acd19f3ef69d0b0139b32c7b2634686e794d4351368542899f2d1fe8448c97141969dfd2d13fb32fb7a5ea84dc9e33e03e86b2523cae600732a910cc9491d299290c494eec4965deda04409f99617ed2c4feb644efa799cdb299fbed3738394c69277af037df9d763692c6cf9224d5d5456f3b78f4e990846d6559edc29edb010a1bdc9c9759e7c1ff63b1b68fa03767bdaa1f33a1cc2caacf3ad8db2d4a51856cd6640ac5b5daaed29e479768c95453219c3ac7b8d5a6497b35d41d2fb5279511ecfa62510099186fc97e2f93cddf93ecdf33a9df37ece3033bb26bf7812217dd3998e6b5d0d2e39711792d0325aa5698dbefddbb10b1dd0009d1f99dde87aaabc1156ca9cf193c3148556ba93a9dc071501b81ba5cb7863fea8c095bf7d97f5149957dc35f18bf03dd60a88bebd766ce8e1e6a0a050d2911e594838f9494c05b1455d0d06d308f1293fa52bd8d55fce8b5de98b676706bc5940fdec6ef54dde811ec4b042cda949ced3329fd4d581a2c120", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3c311d57d4daf52904616cf69648081e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x3c311d57d4daf52904616cf69648081e5e0621c4869aa60c02be9adcc98a0d1d": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0x3f1467a096bcd71a5b6a0c8155e20810308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc": "0x000064a7b3b6e00d0000000000000000", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x45323df7cc47150b3930e2666b0aa3134e7b9012096b41c4eb3aaf947f6ea429": "0x0200", + "0x4dcb50595177a3177648411a42aca0f54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424384e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x57f8dc2f5ab09467896f47300f0424385e0621c4869aa60c02be9adcc98a0d1d": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x79e2fe5d327165001f8232643023ed8b4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7b3237373ffdfeb1cab4222e3b520d6b4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xb8753e9383841da95f7b8871e5de32694e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00000000000000000000000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb333f71360101c6556bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c": "0xbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3fe54f28cc5d1fc584acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28": "0x4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950133cc54b617fa6116175726180bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c": "0xbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950b8cd4c3513e0fa5761757261804acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28": "0x4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x084acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe284acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28bc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252cbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xe38f185207498abb5c213d0fb059b3d84e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xe38f185207498abb5c213d0fb059b3d86323ae84c43568be0d1394d5d0d522c4": "0x04000000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} diff --git a/cumulus/parachains/common/Cargo.toml b/cumulus/parachains/common/Cargo.toml index fe5e24ee6079e6e0483efe7455ab86af21ae112e..ebc9f822beb2c0069276876161158243501ab288 100644 --- a/cumulus/parachains/common/Cargo.toml +++ b/cumulus/parachains/common/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"], default-features = false } -log = { version = "0.4.19", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate diff --git a/cumulus/parachains/common/src/xcm_config.rs b/cumulus/parachains/common/src/xcm_config.rs index 15b090923d501b3769a18efe1916a146d4474f42..a9756af7aed245ea792d12addbbb2fff529eedaf 100644 --- a/cumulus/parachains/common/src/xcm_config.rs +++ b/cumulus/parachains/common/src/xcm_config.rs @@ -45,8 +45,6 @@ where >::AssetId, >::Balance, >, - AccountIdOf: - From + Into, { fn charge_weight_in_fungibles( asset_id: as Inspect< diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs index 45e1e94de0100bf8a765b22163f519d0a54ced1c..55437645b0523b577c2e9d455952f5526ba9df0b 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs @@ -25,8 +25,7 @@ use polkadot_primitives::{AssignmentId, ValidatorId}; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, get_account_id_from_seed, get_from_seed, get_host_config, - validators, + accounts, build_genesis_storage, get_account_id_from_seed, get_host_config, validators, }; use parachains_common::Balance; use rococo_runtime_constants::currency::UNITS as ROC; @@ -71,7 +70,7 @@ pub fn genesis() -> Storage { x.4.clone(), x.5.clone(), x.6.clone(), - get_from_seed::("Alice"), + x.7.clone(), ), ) }) @@ -79,7 +78,7 @@ pub fn genesis() -> Storage { }, babe: rococo_runtime::BabeConfig { authorities: Default::default(), - epoch_config: Some(rococo_runtime::BABE_GENESIS_EPOCH_CONFIG), + epoch_config: rococo_runtime::BABE_GENESIS_EPOCH_CONFIG, ..Default::default() }, sudo: rococo_runtime::SudoConfig { diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs index e2297100a4525e6d26cb469f6f1458023a12b82c..700b80e63f6cf68e6095b7e02d84bb285ce720f9 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs @@ -26,7 +26,7 @@ use polkadot_primitives::{AssignmentId, ValidatorId}; // Cumulus use emulated_integration_tests_common::{ - accounts, build_genesis_storage, get_from_seed, get_host_config, validators, + accounts, build_genesis_storage, get_host_config, validators, }; use parachains_common::Balance; use westend_runtime_constants::currency::UNITS as WND; @@ -72,7 +72,7 @@ pub fn genesis() -> Storage { x.4.clone(), x.5.clone(), x.6.clone(), - get_from_seed::("Alice"), + x.7.clone(), ), ) }) @@ -94,7 +94,7 @@ pub fn genesis() -> Storage { }, babe: westend_runtime::BabeConfig { authorities: Default::default(), - epoch_config: Some(westend_runtime::BABE_GENESIS_EPOCH_CONFIG), + epoch_config: westend_runtime::BABE_GENESIS_EPOCH_CONFIG, ..Default::default() }, configuration: westend_runtime::ConfigurationConfig { config: get_host_config() }, diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index 381e4110f908a5669b20acbaea44351169652ade..721c58fd86481ca7269db8567c1cc0fd3507a92b 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -14,6 +14,7 @@ codec = { package = "parity-scale-codec", version = "3.4.0", default-features = paste = "1.0.14" # Substrate +beefy-primitives = { package = "sp-consensus-beefy", path = "../../../../../substrate/primitives/consensus/beefy" } grandpa = { package = "sc-consensus-grandpa", path = "../../../../../substrate/client/consensus/grandpa" } sp-authority-discovery = { path = "../../../../../substrate/primitives/authority-discovery" } sp-runtime = { path = "../../../../../substrate/primitives/runtime" } @@ -25,7 +26,6 @@ pallet-balances = { path = "../../../../../substrate/frame/balances" } pallet-message-queue = { path = "../../../../../substrate/frame/message-queue" } # Polkadot -polkadot-service = { path = "../../../../../polkadot/node/service", default-features = false, features = ["full-node"] } polkadot-primitives = { path = "../../../../../polkadot/primitives" } polkadot-runtime-parachains = { path = "../../../../../polkadot/runtime/parachains" } xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm" } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index 546f81ce8254e3565ad03987d4376da7971782cc..1a5cc1f6fea6dde1882180d8389e9bcc7773d023 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -20,6 +20,7 @@ pub mod xcm_helpers; pub use xcm_emulator; // Substrate +use beefy_primitives::ecdsa_crypto::AuthorityId as BeefyId; use grandpa::AuthorityId as GrandpaId; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; @@ -36,7 +37,6 @@ use polkadot_runtime_parachains::configuration::HostConfiguration; // Cumulus use parachains_common::{AccountId, AuraId}; use polkadot_primitives::{AssignmentId, ValidatorId}; -use polkadot_service::chain_spec::get_authority_keys_from_seed_no_beefy; pub const XCM_V2: u32 = 2; pub const XCM_V3: u32 = 3; @@ -152,7 +152,18 @@ pub mod validators { ValidatorId, AssignmentId, AuthorityDiscoveryId, + BeefyId, )> { - vec![get_authority_keys_from_seed_no_beefy("Alice")] + let seed = "Alice"; + vec![( + get_account_id_from_seed::(&format!("{}//stash", seed)), + get_account_id_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + get_from_seed::(seed), + )] } } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index 01a376e4dbf8cb304b55951540613b167996d4c8..d3bb3238a3b4308ca010cf1fa5fe8cd65ab2b74c 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -76,7 +76,7 @@ macro_rules! test_parachain_is_trusted_teleporter { $crate::macros::cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. } ) => {}, RuntimeEvent::Balances( - $crate::macros::pallet_balances::Event::Withdraw { who: sender, amount } + $crate::macros::pallet_balances::Event::Burned { who: sender, amount } ) => {}, ] ); @@ -90,7 +90,7 @@ macro_rules! test_parachain_is_trusted_teleporter { $receiver_para, vec![ RuntimeEvent::Balances( - $crate::macros::pallet_balances::Event::Deposit { who: receiver, .. } + $crate::macros::pallet_balances::Event::Minted { who: receiver, .. } ) => {}, RuntimeEvent::MessageQueue( $crate::macros::pallet_message_queue::Event::Processed { success: true, .. } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index 50f47ab970bad1de098bafe66f1adc998d64c93c..d2c3a323256c193a3af6230c0a0b33ae3f54198f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -66,7 +66,7 @@ fn para_receiver_assertions(_: Test) { assert_expected_events!( PenpalA, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -82,7 +82,7 @@ fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -101,12 +101,12 @@ fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { vec![ // Amount to reserve transfer is withdrawn from Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_on_ahr.clone().into(), amount: *amount == t.args.amount, }, - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -143,7 +143,7 @@ fn system_para_to_para_assets_receiver_assertions(_: Test) { assert_expected_events!( PenpalA, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::Assets(pallet_assets::Event::Issued { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } @@ -160,7 +160,7 @@ fn para_to_para_sender_assertions(t: ParaToParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -184,14 +184,14 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaTest) { vec![ // Withdrawn from sender parachain SA RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_a_on_rococo, amount: *amount == t.args.amount, }, // Deposited to receiver parachain SA RuntimeEvent::Balances( - pallet_balances::Event::Deposit { who, .. } + pallet_balances::Event::Minted { who, .. } ) => { who: *who == sov_penpal_b_on_rococo, }, @@ -207,7 +207,7 @@ fn para_to_para_receiver_assertions(_: ParaToParaTest) { assert_expected_events!( PenpalB, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs index 218234cc78eaab2801d08f4fa34b99eaaa5935b9..dfb5061b55f053b51e0ceedee3f8df0edbb7b8ba 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/teleport.rs @@ -28,12 +28,12 @@ fn relay_origin_assertions(t: RelayToSystemParaTest) { Rococo, vec![ // Amount to teleport is withdrawn from Sender - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, // Amount to teleport is deposited in Relay's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, @@ -54,12 +54,12 @@ fn relay_dest_assertions(t: SystemParaToRelayTest) { Rococo, vec![ // Amount is withdrawn from Relay Chain's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -88,7 +88,7 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { AssetHubRococo, vec![ // Amount is withdrawn from Sender's account - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, @@ -105,7 +105,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { AssetHubRococo, vec![ // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -122,7 +122,7 @@ fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { PenpalA, vec![ RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -149,12 +149,12 @@ fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { vec![ // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_on_ahr.clone().into(), amount: *amount == t.args.amount, }, - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { @@ -221,7 +221,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { amount: *amount == expected_asset_amount, }, // native asset for fee is deposited to receiver - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, RuntimeEvent::MessageQueue( diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 851d7be6ec9a0af133c13d7fb0d6aab8ae3411fc..a29cd10ba8338c9ca560bad29b18fba3a94b4c4d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -70,7 +70,7 @@ fn para_receiver_assertions(_: Test) { assert_expected_events!( PenpalB, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -88,7 +88,7 @@ fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -109,12 +109,12 @@ fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_on_ahw.clone().into(), amount: *amount == t.args.amount, }, - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, @@ -153,7 +153,7 @@ fn system_para_to_para_assets_receiver_assertions(_: Test) { assert_expected_events!( PenpalB, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::Assets(pallet_assets::Event::Issued { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } @@ -170,7 +170,7 @@ fn para_to_para_sender_assertions(t: ParaToParaTest) { vec![ // Amount to reserve transfer is transferred to Parachain's Sovereign account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -194,14 +194,14 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaTest) { vec![ // Withdrawn from sender parachain SA RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_b_on_westend, amount: *amount == t.args.amount, }, // Deposited to receiver parachain SA RuntimeEvent::Balances( - pallet_balances::Event::Deposit { who, .. } + pallet_balances::Event::Minted { who, .. } ) => { who: *who == sov_penpal_a_on_westend, }, @@ -217,7 +217,7 @@ fn para_to_para_receiver_assertions(_: ParaToParaTest) { assert_expected_events!( PenpalA, vec![ - RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {}, RuntimeEvent::MessageQueue( pallet_message_queue::Event::Processed { success: true, .. } ) => {}, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs index 01498f7bb4e3b525d5edff164a7fb18a035517e4..0dd1a1533b55904e4fb99dd0d9e3c6face43b19d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/teleport.rs @@ -28,12 +28,12 @@ fn relay_origin_assertions(t: RelayToSystemParaTest) { Westend, vec![ // Amount to teleport is withdrawn from Sender - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, // Amount to teleport is deposited in Relay's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, @@ -54,12 +54,12 @@ fn relay_dest_assertions(t: SystemParaToRelayTest) { Westend, vec![ // Amount is withdrawn from Relay Chain's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -88,7 +88,7 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { AssetHubWestend, vec![ // Amount is withdrawn from Sender's account - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, @@ -105,7 +105,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { AssetHubWestend, vec![ // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -122,7 +122,7 @@ fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) { PenpalB, vec![ RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, @@ -149,12 +149,12 @@ fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) { vec![ // native asset reserve transfer for paying fees, withdrawn from Penpal's sov account RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_penpal_on_ahr.clone().into(), amount: *amount == t.args.amount, }, - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { @@ -221,7 +221,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) { amount: *amount == expected_asset_amount, }, // native asset for fee is deposited to receiver - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, RuntimeEvent::MessageQueue( diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml index ddffe7686a2f188870187aad2af7bae25364efb2..89f0d2a9ca6dacae72e73d5b6e8c310347389070 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml @@ -44,9 +44,9 @@ rococo-system-emulated-network = { path = "../../../networks/rococo-system" } asset-hub-rococo-runtime = { path = "../../../../../runtimes/assets/asset-hub-rococo", default-features = false } # Snowbridge -snowbridge-core = { path = "../../../../../../../bridges/snowbridge/parachain/primitives/core", default-features = false } -snowbridge-router-primitives = { path = "../../../../../../../bridges/snowbridge/parachain/primitives/router", default-features = false } -snowbridge-pallet-system = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/system", default-features = false } -snowbridge-pallet-outbound-queue = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/outbound-queue", default-features = false } -snowbridge-pallet-inbound-queue = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/inbound-queue", default-features = false } -snowbridge-pallet-inbound-queue-fixtures = { path = "../../../../../../../bridges/snowbridge/parachain/pallets/inbound-queue/fixtures" } +snowbridge-core = { path = "../../../../../../../bridges/snowbridge/primitives/core", default-features = false } +snowbridge-router-primitives = { path = "../../../../../../../bridges/snowbridge/primitives/router", default-features = false } +snowbridge-pallet-system = { path = "../../../../../../../bridges/snowbridge/pallets/system", default-features = false } +snowbridge-pallet-outbound-queue = { path = "../../../../../../../bridges/snowbridge/pallets/outbound-queue", default-features = false } +snowbridge-pallet-inbound-queue = { path = "../../../../../../../bridges/snowbridge/pallets/inbound-queue", default-features = false } +snowbridge-pallet-inbound-queue-fixtures = { path = "../../../../../../../bridges/snowbridge/pallets/inbound-queue/fixtures" } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index a203de0f8c930a43c1848bfa9179e0cba40689a9..787a82ed32f7376f1c94c584711098c15d4da198 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -184,13 +184,13 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() { vec![ // WND is withdrawn from AHR's SA on AHW RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_ahr_on_ahw, amount: *amount == amount_to_send, }, // WNDs deposited to beneficiary - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == AssetHubWestendReceiver::get(), }, // message processed successfully diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs index a33d2fab7536cfffe46a57548a21fb5e7f0391e4..6d7b53c8fdfdb3660a4888df5dfd380bccc9d0dd 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs @@ -68,7 +68,7 @@ pub(crate) fn assert_bridge_hub_rococo_message_accepted(expected_processed: bool BridgeHubRococo, vec![ // pay for bridge fees - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Burned { .. }) => {}, // message exported RuntimeEvent::BridgeWestendMessages( pallet_bridge_messages::Event::MessageAccepted { .. } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index 5e1a2af660b0c8ae56cf7f760785052918ecd6d3..a5957ee339306f580b7906b25d3adfff4a78deb1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -478,7 +478,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Deposit{ who, amount }) + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) if *who == TREASURY_ACCOUNT.into() && *amount == 16903333 )), "Snowbridge sovereign takes local fee." @@ -487,7 +487,7 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { assert!( events.iter().any(|event| matches!( event, - RuntimeEvent::Balances(pallet_balances::Event::Deposit{ who, amount }) + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) if *who == assethub_sovereign && *amount == 2680000000000, )), "AssetHub sovereign takes remote fee." diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index c2a9c008902222f9d9a1206b59b3f510147ddbea..5b0990973d2103f7fa606c4abcccd41a893067d2 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -182,13 +182,13 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() { vec![ // ROC is withdrawn from AHW's SA on AHR RuntimeEvent::Balances( - pallet_balances::Event::Withdraw { who, amount } + pallet_balances::Event::Burned { who, amount } ) => { who: *who == sov_ahw_on_ahr, amount: *amount == amount_to_send, }, // ROCs deposited to beneficiary - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == AssetHubRococoReceiver::get(), }, // message processed successfully diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 186b96b3976926b6f75995cc47e8e4c9c63b6f48..3074435e8e4e03e561388e5fb0bdf00d5b3f511f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -67,7 +67,7 @@ pub(crate) fn assert_bridge_hub_westend_message_accepted(expected_processed: boo BridgeHubWestend, vec![ // pay for bridge fees - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {}, + RuntimeEvent::Balances(pallet_balances::Event::Burned { .. }) => {}, // message exported RuntimeEvent::BridgeRococoMessages( pallet_bridge_messages::Event::MessageAccepted { .. } diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs index 58bb9504dbd6bdaffc25c4f8b96424a96309d20a..87adb363e022b3c21fdfca052bb7a3019658760c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/reap_identity.rs @@ -366,7 +366,7 @@ fn assert_reap_events(id_deposit: Balance, id: &Identity) { PeopleRococo, vec![ // Deposit and Endowed from teleport - RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Minted { .. }) => {}, RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, // Amount reserved for identity info RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { @@ -392,7 +392,7 @@ fn assert_reap_events(id_deposit: Balance, id: &Identity) { PeopleRococo, vec![ // Deposit and Endowed from teleport - RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Minted { .. }) => {}, RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, // Amount reserved for identity info RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs index 42c7e310b2628eb97011ff36f6c8db2b3dbd130c..0a12277395d739b032d8de032dcdf9f405eaf158 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/src/tests/teleport.rs @@ -25,12 +25,12 @@ fn relay_origin_assertions(t: RelayToSystemParaTest) { Rococo, vec![ // Amount to teleport is withdrawn from Sender - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, // Amount to teleport is deposited in Relay's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, @@ -51,12 +51,12 @@ fn relay_dest_assertions(t: SystemParaToRelayTest) { Rococo, vec![ // Amount is withdrawn from Relay Chain's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -85,7 +85,7 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { PeopleRococo, vec![ // Amount is withdrawn from Sender's account - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, @@ -102,7 +102,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { PeopleRococo, vec![ // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs index 246c612a6810c053c4912c9c15f12c8e051eb06c..8d63c8ceff6efea9825cb0d009da74d2974ba40b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/reap_identity.rs @@ -368,7 +368,7 @@ fn assert_reap_events(id_deposit: Balance, id: &Identity) { PeopleWestend, vec![ // Deposit and Endowed from teleport - RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Minted { .. }) => {}, RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, // Amount reserved for identity info RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { @@ -394,7 +394,7 @@ fn assert_reap_events(id_deposit: Balance, id: &Identity) { PeopleWestend, vec![ // Deposit and Endowed from teleport - RuntimeEvent::Balances(BalancesEvent::Deposit { .. }) => {}, + RuntimeEvent::Balances(BalancesEvent::Minted { .. }) => {}, RuntimeEvent::Balances(BalancesEvent::Endowed { .. }) => {}, // Amount reserved for identity info RuntimeEvent::Balances(BalancesEvent::Reserved { who, amount }) => { diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs index e9f0158efbf5e7c344125463991072f3fe77c02f..345663be99baa66fc3e871abc43695f8c681308f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/teleport.rs @@ -25,12 +25,12 @@ fn relay_origin_assertions(t: RelayToSystemParaTest) { Westend, vec![ // Amount to teleport is withdrawn from Sender - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, // Amount to teleport is deposited in Relay's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, @@ -51,12 +51,12 @@ fn relay_dest_assertions(t: SystemParaToRelayTest) { Westend, vec![ // Amount is withdrawn from Relay Chain's `CheckAccount` - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == ::XcmPallet::check_account(), amount: *amount == t.args.amount, }, // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] @@ -85,7 +85,7 @@ fn para_origin_assertions(t: SystemParaToRelayTest) { PeopleWestend, vec![ // Amount is withdrawn from Sender's account - RuntimeEvent::Balances(pallet_balances::Event::Withdraw { who, amount }) => { + RuntimeEvent::Balances(pallet_balances::Event::Burned { who, amount }) => { who: *who == t.sender.account_id, amount: *amount == t.args.amount, }, @@ -102,7 +102,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) { PeopleWestend, vec![ // Amount minus fees are deposited in Receiver's account - RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => { + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => { who: *who == t.receiver.account_id, }, ] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index cb6445a6316ab5654a77ded15e7a9f3c49b005ce..05936e93993231429d738edf15acc48150bcc542 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate @@ -90,7 +90,7 @@ bp-asset-hub-rococo = { path = "../../../../../bridges/primitives/chain-asset-hu bp-asset-hub-westend = { path = "../../../../../bridges/primitives/chain-asset-hub-westend", default-features = false } bp-bridge-hub-rococo = { path = "../../../../../bridges/primitives/chain-bridge-hub-rococo", default-features = false } bp-bridge-hub-westend = { path = "../../../../../bridges/primitives/chain-bridge-hub-westend", default-features = false } -snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/parachain/primitives/router", default-features = false } +snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/primitives/router", default-features = false } [dev-dependencies] asset-test-utils = { path = "../test-utils" } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index d67595b10b4e21831ef05aad9fe4ac002b7a7ff8..81bb66a56a29a6d4f0669234aadfcf1b55157275 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -62,7 +62,7 @@ use frame_support::{ ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter, TransformOrigin, }, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, Weight}, + weights::{ConstantMultiplier, Weight}, BoundedVec, PalletId, }; use frame_system::{ @@ -75,11 +75,10 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, CollectionId, Hash, - Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, - NORMAL_DISPATCH_RATIO, + Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use sp_runtime::{Perbill, RuntimeDebug}; -use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, ForeignCreatorsSovereignAccountOf, GovernanceLocation, PoolAssetsConvertedConcreteId, TokenLocation, TokenLocationV3, @@ -114,7 +113,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 14, @@ -127,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("statemine"), impl_name: create_runtime_str!("statemine"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 14, @@ -140,28 +139,6 @@ pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } -/// We allow for 2 seconds of compute with a 6 second average block. -const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( - WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), - cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, -); - -/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included -/// into the relay chain. -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; -/// How many parachain blocks are processed by the relay chain per parent. Limits the -/// number of blocks authored per slot. -const BLOCK_PROCESSING_VELOCITY: u32 = 1; - -/// This determines the average expected block time that we are targeting. -/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. -/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked -/// up by `pallet_aura` to implement `fn slot_duration()`. -/// -/// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 6000; -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; - parameter_types! { pub const Version: RuntimeVersion = VERSION; pub RuntimeBlockLength: BlockLength = @@ -406,8 +383,8 @@ pub type ForeignAssetsInstance = pallet_assets::Instance2; impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; - type AssetId = xcm::v3::MultiLocation; - type AssetIdParameter = xcm::v3::MultiLocation; + type AssetId = xcm::v3::Location; + type AssetIdParameter = xcm::v3::Location; type Currency = Balances; type CreateOrigin = ForeignCreators< ( @@ -991,6 +968,8 @@ pub type Migrations = ( InitStorageVersions, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Migration to initialize storage versions for pallets added after genesis. @@ -1064,6 +1043,7 @@ mod benches { [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] [pallet_balances, Balances] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_nft_fractionalization, NftFractionalization] [pallet_nfts, Nfts] @@ -1073,6 +1053,7 @@ mod benches { [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_xcm_bridge_hub_router, ToWestend] // XCM @@ -1374,8 +1355,31 @@ impl_runtime_apis! { Config as XcmBridgeHubRouterConfig, }; + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + TokenLocation::get(), + ExistentialDeposit::get() + ).into()); + pub const RandomParaId: ParaId = ParaId::new(43211234); + } + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -1384,7 +1388,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between AH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), @@ -1392,17 +1396,13 @@ impl_runtime_apis! { } fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { - // AH can reserve transfer native token to some random parachain. - let random_para_id = 43211234; - ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests( - random_para_id.into() - ); Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, - ParentThen(Parachain(random_para_id).into()).into(), + // AH can reserve transfer native token to some random parachain. + ParentThen(Parachain(RandomParaId::get().into()).into()).into(), )) } @@ -1488,13 +1488,6 @@ impl_runtime_apis! { use xcm_config::{TokenLocation, MaxAssetsIntoHolding}; use pallet_xcm_benchmarks::asset_instance_from; - parameter_types! { - pub ExistentialDepositAsset: Option = Some(( - TokenLocation::get(), - ExistentialDeposit::get() - ).into()); - } - impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 693fd7c3fc78aaab44d468fe355090c6455b1404..2584dbdf31062f4c75f03714ce803d234ced0be9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -49,19 +49,18 @@ use testnet_parachains_constants::rococo::snowbridge::{ EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX, }; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, - FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, - LocalMint, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignPaidRemoteExporter, - SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + FungibleAdapter, FungiblesAdapter, GlobalConsensusParachainConvertsFor, HashedDescription, + IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, NonFungiblesAdapter, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignPaidRemoteExporter, SovereignSignedViaLocation, StartsWith, + StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -114,8 +113,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -223,7 +221,7 @@ pub type PoolFungiblesTransactor = FungiblesAdapter< /// Means for transacting assets on this chain. pub type AssetTransactors = ( - CurrencyTransactor, + FungibleTransactor, FungiblesTransactor, ForeignFungiblesTransactor, PoolFungiblesTransactor, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs index b70758eb653dde267bafcbbadc4b90609f33c519..38c118dbb4b25fe2e345e9247a2a253d0becd613 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -22,13 +22,13 @@ use asset_hub_rococo_runtime::{ xcm_config::{ bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf, - LocationToAccountId, TokenLocation, TokenLocationV3, TrustBackedAssetsPalletLocation, - TrustBackedAssetsPalletLocationV3, XcmConfig, + LocationToAccountId, StakingPot, TokenLocation, TokenLocationV3, + TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3, XcmConfig, }, AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, - MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, - ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, SLOT_DURATION, + MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, + SessionKeys, ToWestendXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, }; use asset_test_utils::{ test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, @@ -49,10 +49,9 @@ use frame_support::{ use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; +use sp_std::ops::Mul; use std::convert::Into; -use testnet_parachains_constants::rococo::{ - consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, currency::UNITS, fee::WeightToFee, -}; +use testnet_parachains_constants::rococo::{consensus::*, currency::UNITS, fee::WeightToFee}; use xcm::latest::prelude::{Assets as XcmAssets, *}; use xcm_builder::V4V3LocationConverter; use xcm_executor::traits::{JustTry, WeightTrader}; @@ -87,6 +86,52 @@ fn slot_durations() -> SlotDurations { } } +fn setup_pool_for_paying_fees_with_foreign_assets( + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountId, + xcm::v3::Location, + Balance, + ), +) { + let existential_deposit = ExistentialDeposit::get(); + + // setup a pool to pay fees with `foreign_asset_id_location` tokens + let pool_owner: AccountId = [14u8; 32].into(); + let native_asset = xcm::v3::Location::parent(); + let pool_liquidity: Balance = + existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); + + let _ = Balances::force_set_balance( + RuntimeOrigin::root(), + pool_owner.clone().into(), + (existential_deposit + pool_liquidity).mul(2).into(), + ); + + assert_ok!(ForeignAssets::mint( + RuntimeOrigin::signed(foreign_asset_owner), + foreign_asset_id_location.into(), + pool_owner.clone().into(), + (foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(), + )); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()), + pool_liquidity, + pool_liquidity, + 1, + 1, + pool_owner, + )); +} + #[test] fn test_buy_and_refund_weight_in_native() { ExtBuilder::::default() @@ -1058,7 +1103,8 @@ fn limited_reserve_transfer_assets_for_native_asset_over_bridge_works( mod asset_hub_rococo_tests { use super::*; - use asset_hub_rococo_runtime::{PolkadotXcm, RuntimeOrigin}; + use asset_hub_rococo_runtime::PolkadotXcm; + use xcm_executor::traits::ConvertLocation; fn bridging_to_asset_hub_westend() -> TestBridgingConfig { let _ = PolkadotXcm::force_xcm_version( @@ -1083,27 +1129,135 @@ mod asset_hub_rococo_tests { } #[test] - fn receive_reserve_asset_deposited_wnd_from_asset_hub_westend_works() { + fn receive_reserve_asset_deposited_wnd_from_asset_hub_westend_fees_paid_by_pool_swap_works() { const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = xcm::v3::Location::new( + 2, + [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Westend)], + ); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = + LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, AllPalletsWithoutSystem, XcmConfig, - LocationToAccountId, ForeignAssetsInstance, >( collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), ExistentialDeposit::get(), AccountId::from([73; 32]), - AccountId::from(BLOCK_AUTHOR_ACCOUNT), + block_author_account, // receiving WNDs - (xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Westend)]), 1000000000000, 1_000_000_000), + foreign_asset_create_params.clone(), + 1000000000000, + || { + // setup pool for paying fees to touch `SwapFirstAssetTrader` + setup_pool_for_paying_fees_with_foreign_assets(foreign_asset_create_params); + // staking pot account for collecting local native fees from `BuyExecution` + let _ = Balances::force_set_balance(RuntimeOrigin::root(), StakingPot::get().into(), ExistentialDeposit::get()); + // prepare bridge configuration + bridging_to_asset_hub_westend() + }, + ( + [PalletInstance(bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Westend), + [Parachain(1000)].into() + ), + || { + // check staking pot for ED + assert_eq!(Balances::free_balance(&staking_pot), ExistentialDeposit::get()); + // check now foreign asset for staking pot + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + }, + || { + // `SwapFirstAssetTrader` - staking pot receives xcm fees in ROCs + assert!( + Balances::free_balance(&staking_pot) > ExistentialDeposit::get() + ); + // staking pot receives no foreign assets + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + } + ) + } + + #[test] + fn receive_reserve_asset_deposited_wnd_from_asset_hub_westend_fees_paid_by_sufficient_asset_works( + ) { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = xcm::v3::Location::new( + 2, + [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Westend)], + ); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = + LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + block_author_account.clone(), + // receiving WNDs + foreign_asset_create_params, + 1000000000000, bridging_to_asset_hub_westend, ( [PalletInstance(bp_bridge_hub_rococo::WITH_BRIDGE_ROCOCO_TO_WESTEND_MESSAGES_PALLET_INDEX)].into(), GlobalConsensus(Westend), [Parachain(1000)].into() - ) + ), + || { + // check block author before + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ), + 0 + ); + }, + || { + // `TakeFirstAssetTrader` puts fees to the block author + assert!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ) > 0 + ); + // `SwapFirstAssetTrader` did not work + assert_eq!(Balances::free_balance(&staking_pot), 0); + } ) } @@ -1263,6 +1417,12 @@ fn change_xcm_bridge_hub_router_base_fee_by_governance_works() { 1000, Box::new(|call| RuntimeCall::System(call).encode()), || { + log::error!( + target: "bridges::estimate", + "`bridging::XcmBridgeHubRouterBaseFee` actual value: {} for runtime: {}", + bridging::XcmBridgeHubRouterBaseFee::get(), + ::Version::get(), + ); ( bridging::XcmBridgeHubRouterBaseFee::key().to_vec(), bridging::XcmBridgeHubRouterBaseFee::get(), @@ -1289,6 +1449,12 @@ fn change_xcm_bridge_hub_ethereum_base_fee_by_governance_works() { 1000, Box::new(|call| RuntimeCall::System(call).encode()), || { + log::error!( + target: "bridges::estimate", + "`bridging::BridgeHubEthereumBaseFee` actual value: {} for runtime: {}", + bridging::to_ethereum::BridgeHubEthereumBaseFee::get(), + ::Version::get(), + ); ( bridging::to_ethereum::BridgeHubEthereumBaseFee::key().to_vec(), bridging::to_ethereum::BridgeHubEthereumBaseFee::get(), diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 62db0b454db0b83b7ccf47ef89a3e71bd0a61fb2..78c48507a7a44efadd39ab7729f9cadfe681cf8a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate @@ -73,6 +73,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = ["bridging"] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -175,6 +176,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 6424cec675ddc5c2823bfe482407cd2051f71ee8..3f58c679eeedd0b5033573f5982316d27b09252a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -32,7 +32,7 @@ use assets_common::{ AssetIdForTrustBackedAssetsConvert, }; use codec::{Decode, Encode, MaxEncodedLen}; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -58,7 +58,7 @@ use pallet_xcm::EnsureXcm; use parachains_common::{ impls::DealWithFees, message_queue::*, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, CollectionId, Hash, Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, - DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + NORMAL_DISPATCH_RATIO, }; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; @@ -72,7 +72,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, PoolAssetsConvertedConcreteId, TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocationV3, WestendLocation, @@ -110,7 +110,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westmint"), impl_name: create_runtime_str!("westmint"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 14, @@ -171,6 +171,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -604,15 +607,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} parameter_types! { @@ -697,9 +702,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -944,6 +949,8 @@ pub type Migrations = ( DeleteUndecodableStorage, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Asset Hub Westend has some undecodable storage, delete it. @@ -980,7 +987,7 @@ impl frame_support::traits::OnRuntimeUpgrade for DeleteUndecodableStorage { }, Err(e) => { log::error!("Failed to destroy undecodable NFT item: {:?}", e); - return ::DbWeight::get().reads_writes(0, writes) + return ::DbWeight::get().reads_writes(0, writes); }, } @@ -992,7 +999,7 @@ impl frame_support::traits::OnRuntimeUpgrade for DeleteUndecodableStorage { }, Err(e) => { log::error!("Failed to destroy undecodable NFT item: {:?}", e); - return ::DbWeight::get().reads_writes(0, writes) + return ::DbWeight::get().reads_writes(0, writes); }, } @@ -1077,6 +1084,7 @@ mod benches { [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_xcm_bridge_hub_router, ToRococo] // XCM @@ -1090,7 +1098,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -1098,6 +1106,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION @@ -1410,8 +1427,31 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + WestendLocation::get(), + ExistentialDeposit::get() + ).into()); + pub const RandomParaId: ParaId = ParaId::new(43211234); + } + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >, + polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + PriceForSiblingParachainDelivery, + RandomParaId, + ParachainSystem, + > + ); + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -1420,7 +1460,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between AH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), @@ -1428,17 +1468,13 @@ impl_runtime_apis! { } fn reserve_transferable_asset_and_dest() -> Option<(Asset, Location)> { - // AH can reserve transfer native token to some random parachain. - let random_para_id = 43211234; - ParachainSystem::open_outbound_hrmp_channel_for_benchmarks_or_tests( - random_para_id.into() - ); Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, - ParentThen(Parachain(random_para_id).into()).into(), + // AH can reserve transfer native token to some random parachain. + ParentThen(Parachain(RandomParaId::get().into()).into()).into(), )) } @@ -1529,13 +1565,6 @@ impl_runtime_apis! { use xcm_config::{MaxAssetsIntoHolding, WestendLocation}; use pallet_xcm_benchmarks::asset_instance_from; - parameter_types! { - pub ExistentialDepositAsset: Option = Some(( - WestendLocation::get(), - ExistentialDeposit::get() - ).into()); - } - impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; type AccountIdConverter = xcm_config::LocationToAccountId; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 01437534c1fde75439546a0c84275b83db796099..50865c0006117ac619a757fc48ce5cf9db4516b9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -45,19 +45,18 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::{AccountIdConversion, ConvertInto}; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal, EnsureXcmOrigin, - FrameTransactionalProcessor, FungiblesAdapter, GlobalConsensusParachainConvertsFor, - HashedDescription, IsConcrete, LocalMint, NetworkExportTableItem, NoChecking, - NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, - SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, - TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, - WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, + GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint, + NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, + StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -107,8 +106,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -216,7 +214,7 @@ pub type PoolFungiblesTransactor = FungiblesAdapter< /// Means for transacting assets on this chain. pub type AssetTransactors = ( - CurrencyTransactor, + FungibleTransactor, FungiblesTransactor, ForeignFungiblesTransactor, PoolFungiblesTransactor, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 7be21c99b986bdde126d863a6bcdfb2aef54ac0b..aa8c3cf2f14db33e119a355bff2e61ccc9a2c23d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -22,8 +22,8 @@ use asset_hub_westend_runtime::{ xcm_config::{ bridging, AssetFeeAsExistentialDepositMultiplierFeeCharger, CheckingAccount, ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, ForeignCreatorsSovereignAccountOf, - LocationToAccountId, TrustBackedAssetsPalletLocation, TrustBackedAssetsPalletLocationV3, - WestendLocation, WestendLocationV3, XcmConfig, + LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation, + TrustBackedAssetsPalletLocationV3, WestendLocation, WestendLocationV3, XcmConfig, }, AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, @@ -47,16 +47,14 @@ use frame_support::{ }, weights::{Weight, WeightToFee as WeightToFeeT}, }; -use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, SLOT_DURATION}; +use parachains_common::{AccountId, AssetIdForTrustBackedAssets, AuraId, Balance}; use sp_consensus_aura::SlotDuration; use sp_runtime::traits::MaybeEquivalence; -use std::convert::Into; -use testnet_parachains_constants::westend::{ - consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, currency::UNITS, fee::WeightToFee, -}; +use std::{convert::Into, ops::Mul}; +use testnet_parachains_constants::westend::{consensus::*, currency::UNITS, fee::WeightToFee}; use xcm::latest::prelude::{Assets as XcmAssets, *}; use xcm_builder::V4V3LocationConverter; -use xcm_executor::traits::{JustTry, WeightTrader}; +use xcm_executor::traits::{ConvertLocation, JustTry, WeightTrader}; const ALICE: [u8; 32] = [1u8; 32]; const SOME_ASSET_ADMIN: [u8; 32] = [5u8; 32]; @@ -88,6 +86,52 @@ fn slot_durations() -> SlotDurations { } } +fn setup_pool_for_paying_fees_with_foreign_assets( + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountId, + xcm::v3::Location, + Balance, + ), +) { + let existential_deposit = ExistentialDeposit::get(); + + // setup a pool to pay fees with `foreign_asset_id_location` tokens + let pool_owner: AccountId = [14u8; 32].into(); + let native_asset = xcm::v3::Location::parent(); + let pool_liquidity: Balance = + existential_deposit.max(foreign_asset_id_minimum_balance).mul(100_000); + + let _ = Balances::force_set_balance( + RuntimeOrigin::root(), + pool_owner.clone().into(), + (existential_deposit + pool_liquidity).mul(2).into(), + ); + + assert_ok!(ForeignAssets::mint( + RuntimeOrigin::signed(foreign_asset_owner), + foreign_asset_id_location.into(), + pool_owner.clone().into(), + (foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(), + )); + + assert_ok!(AssetConversion::create_pool( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()) + )); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(pool_owner.clone()), + Box::new(native_asset.into()), + Box::new(foreign_asset_id_location.into()), + pool_liquidity, + pool_liquidity, + 1, + 1, + pool_owner, + )); +} + #[test] fn test_buy_and_refund_weight_in_native() { ExtBuilder::::default() @@ -1073,30 +1117,133 @@ fn limited_reserve_transfer_assets_for_native_asset_to_asset_hub_rococo_works() Some(xcm_config::TreasuryAccount::get()), ) } + #[test] -fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_works() { +fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_pool_swap_works() { const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = + xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< Runtime, AllPalletsWithoutSystem, XcmConfig, - LocationToAccountId, ForeignAssetsInstance, >( collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), ExistentialDeposit::get(), AccountId::from([73; 32]), - AccountId::from(BLOCK_AUTHOR_ACCOUNT), + block_author_account.clone(), // receiving ROCs - (xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]), 1000000000000, 1_000_000_000), - bridging_to_asset_hub_rococo, + foreign_asset_create_params.clone(), + 1000000000000, + || { + // setup pool for paying fees to touch `SwapFirstAssetTrader` + setup_pool_for_paying_fees_with_foreign_assets(foreign_asset_create_params); + // staking pot account for collecting local native fees from `BuyExecution` + let _ = Balances::force_set_balance(RuntimeOrigin::root(), StakingPot::get().into(), ExistentialDeposit::get()); + // prepare bridge configuration + bridging_to_asset_hub_rococo() + }, ( [PalletInstance(bp_bridge_hub_westend::WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX)].into(), GlobalConsensus(Rococo), [Parachain(1000)].into() - ) + ), + || { + // check staking pot for ED + assert_eq!(Balances::free_balance(&staking_pot), ExistentialDeposit::get()); + // check now foreign asset for staking pot + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + }, + || { + // `SwapFirstAssetTrader` - staking pot receives xcm fees in ROCs + assert!( + Balances::free_balance(&staking_pot) > ExistentialDeposit::get() + ); + // staking pot receives no foreign assets + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &staking_pot + ), + 0 + ); + } ) } + +#[test] +fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_sufficient_asset_works() { + const BLOCK_AUTHOR_ACCOUNT: [u8; 32] = [13; 32]; + let block_author_account = AccountId::from(BLOCK_AUTHOR_ACCOUNT); + let staking_pot = StakingPot::get(); + + let foreign_asset_id_location = + xcm::v3::Location::new(2, [xcm::v3::Junction::GlobalConsensus(xcm::v3::NetworkId::Rococo)]); + let foreign_asset_id_minimum_balance = 1_000_000_000; + // sovereign account as foreign asset owner (can be whoever for this scenario) + let foreign_asset_owner = LocationToAccountId::convert_location(&Location::parent()).unwrap(); + let foreign_asset_create_params = + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance); + + asset_test_utils::test_cases_over_bridge::receive_reserve_asset_deposited_from_different_consensus_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + ForeignAssetsInstance, + >( + collator_session_keys().add(collator_session_key(BLOCK_AUTHOR_ACCOUNT)), + ExistentialDeposit::get(), + AccountId::from([73; 32]), + block_author_account.clone(), + // receiving ROCs + foreign_asset_create_params, + 1000000000000, + bridging_to_asset_hub_rococo, + ( + [PalletInstance(bp_bridge_hub_westend::WITH_BRIDGE_WESTEND_TO_ROCOCO_MESSAGES_PALLET_INDEX)].into(), + GlobalConsensus(Rococo), + [Parachain(1000)].into() + ), + || { + // check block author before + assert_eq!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ), + 0 + ); + }, + || { + // `TakeFirstAssetTrader` puts fees to the block author + assert!( + ForeignAssets::balance( + foreign_asset_id_location.into(), + &block_author_account + ) > 0 + ); + // `SwapFirstAssetTrader` did not work + assert_eq!(Balances::free_balance(&staking_pot), 0); + } + ) +} + #[test] fn report_bridge_status_from_xcm_bridge_router_for_rococo_works() { asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< @@ -1209,6 +1356,38 @@ fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() { ) } +#[test] +fn change_xcm_bridge_hub_router_base_fee_by_governance_works() { + asset_test_utils::test_cases::change_storage_constant_by_governance_works::< + Runtime, + bridging::XcmBridgeHubRouterBaseFee, + Balance, + >( + collator_session_keys(), + 1000, + Box::new(|call| RuntimeCall::System(call).encode()), + || { + log::error!( + target: "bridges::estimate", + "`bridging::XcmBridgeHubRouterBaseFee` actual value: {} for runtime: {}", + bridging::XcmBridgeHubRouterBaseFee::get(), + ::Version::get(), + ); + ( + bridging::XcmBridgeHubRouterBaseFee::key().to_vec(), + bridging::XcmBridgeHubRouterBaseFee::get(), + ) + }, + |old_value| { + if let Some(new_value) = old_value.checked_add(1) { + new_value + } else { + old_value.checked_sub(1).unwrap() + } + }, + ) +} + #[test] fn reserve_transfer_native_asset_to_non_teleport_para_works() { asset_test_utils::test_cases::reserve_transfer_native_asset_to_non_teleport_para_works::< diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index 74e5e44ce15581e8e4df7ad6c49417c2b0fdc756..c9252375cfbf019c27a1ce0d5dc38f11db30cac5 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } impl-trait-for-tuples = "0.2.2" # Substrate diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index a0c912b6f0fe3643f1f516b311fdfd5d401db4d0..fa2752179eb6fd238eb8596d8e3ebddf947680d3 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -371,7 +371,7 @@ mod tests { for (asset, expected_result) in test_data { assert_eq!( - >::matches_fungibles( + >::matches_fungibles( &asset.clone().try_into().unwrap() ), expected_result, diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index f1b3953ce473bf5dc27d63eb9a9404fb9086979d..883c93c97b4de6774e86ee83b84d246dc1427f7f 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -16,8 +16,8 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = frame-support = { path = "../../../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../../../substrate/frame/system", default-features = false } pallet-assets = { path = "../../../../../substrate/frame/assets", default-features = false } -pallet-asset-conversion = { path = "../../../../../substrate/frame/asset-conversion", default-features = false } pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } +pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } sp-io = { path = "../../../../../substrate/primitives/io", default-features = false } sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-features = false } @@ -56,11 +56,11 @@ std = [ "cumulus-primitives-core/std", "frame-support/std", "frame-system/std", - "pallet-asset-conversion/std", "pallet-assets/std", "pallet-balances/std", "pallet-collator-selection/std", "pallet-session/std", + "pallet-timestamp/std", "pallet-xcm-bridge-hub-router/std", "pallet-xcm/std", "parachain-info/std", diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs index 4007d926983ed97a8cae705428c1d61184173cb7..53e10956bd0d1d233555a8e4df1ae23fe351c678 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -70,7 +70,8 @@ pub fn teleports_for_native_asset_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config, + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -350,7 +351,8 @@ pub fn teleports_for_foreign_assets_works< + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -701,7 +703,8 @@ pub fn asset_transactor_transfer_with_local_consensus_currency_works: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, @@ -826,7 +829,8 @@ pub fn asset_transactor_transfer_with_pallet_assets_instance_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, @@ -1093,7 +1097,8 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + pallet_assets::Config, + + pallet_assets::Config + + pallet_timestamp::Config, AccountIdOf: Into<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From, @@ -1422,7 +1427,8 @@ pub fn reserve_transfer_native_asset_to_non_teleport_para_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config, + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs index 4a0f31c449ea61d00b04110008d9984e5e87cbd7..1cce3b647cf0446a2246417b5383594fb501e600 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs @@ -30,7 +30,6 @@ use parachains_runtimes_test_utils::{ SlotDurations, ValidatorIdOf, XcmReceivedFrom, }; use sp_runtime::{traits::StaticLookup, Saturating}; -use sp_std::ops::Mul; use xcm::{latest::prelude::*, VersionedAssets}; use xcm_builder::{CreateMatcher, MatchXcm}; use xcm_executor::{traits::ConvertLocation, XcmExecutor}; @@ -71,7 +70,8 @@ pub fn limited_reserve_transfer_assets_for_native_asset_works< + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config - + cumulus_pallet_xcmp_queue::Config, + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, @@ -323,20 +323,22 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< Runtime, AllPalletsWithoutSystem, XcmConfig, - LocationToAccountId, ForeignAssetsPalletInstance, >( collator_session_keys: CollatorSessionKeys, existential_deposit: BalanceOf, target_account: AccountIdOf, block_author_account: AccountIdOf, - ( - foreign_asset_id_location, - transfered_foreign_asset_id_amount, - foreign_asset_id_minimum_balance, - ): (xcm::v3::Location, u128, u128), - prepare_configuration: fn() -> TestBridgingConfig, + (foreign_asset_owner, foreign_asset_id_location, foreign_asset_id_minimum_balance): ( + AccountIdOf, + xcm::v3::Location, + u128, + ), + foreign_asset_id_amount_to_transfer: u128, + prepare_configuration: impl FnOnce() -> TestBridgingConfig, (bridge_instance, universal_origin, descend_origin): (Junctions, Junction, Junctions), /* bridge adds origin manipulation on the way */ + additional_checks_before: impl FnOnce(), + additional_checks_after: impl FnOnce(), ) where Runtime: frame_system::Config + pallet_balances::Config @@ -347,14 +349,13 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config + pallet_assets::Config - + pallet_asset_conversion::Config, + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]> + From<[u8; 32]>, ValidatorIdOf: From>, BalanceOf: From + Into, XcmConfig: xcm_executor::Config, - LocationToAccountId: ConvertLocation>, >::AssetId: From + Into, >::AssetIdParameter: @@ -365,9 +366,6 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< + Into, <::Lookup as StaticLookup>::Source: From<::AccountId>, - ::AssetKind: - From + Into, - ::Balance: From, ForeignAssetsPalletInstance: 'static, { ExtBuilder::::default() @@ -382,88 +380,31 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< block_author_account.clone().into(), ); - // prepare bridge config - let TestBridgingConfig { local_bridge_hub_location, .. } = prepare_configuration(); - // drip 'ED' user target account let _ = >::deposit_creating( &target_account, existential_deposit, ); - // sovereign account as foreign asset owner (can be whoever for this scenario, doesnt - // matter) - let sovereign_account_as_owner_of_foreign_asset = - LocationToAccountId::convert_location(&Location::parent()).unwrap(); - - // staking pot account for collecting local native fees from `BuyExecution` - let staking_pot = >::account_id(); - let _ = >::deposit_creating( - &staking_pot, - existential_deposit, - ); - // create foreign asset for wrapped/derivated representation assert_ok!( >::force_create( RuntimeHelper::::root_origin(), foreign_asset_id_location.into(), - sovereign_account_as_owner_of_foreign_asset.clone().into(), + foreign_asset_owner.into(), true, // is_sufficient=true foreign_asset_id_minimum_balance.into() ) ); - // setup a pool to pay fees with `foreign_asset_id_location` tokens - let pool_owner: AccountIdOf = [1u8; 32].into(); - let native_asset = xcm::v3::Location::parent(); - let pool_liquidity: u128 = - existential_deposit.into().max(foreign_asset_id_minimum_balance).mul(100_000); - - let _ = >::deposit_creating( - &pool_owner, - (existential_deposit.into() + pool_liquidity).mul(2).into(), - ); - - assert_ok!(>::mint( - RuntimeHelper::::origin_of( - sovereign_account_as_owner_of_foreign_asset - ), - foreign_asset_id_location.into(), - pool_owner.clone().into(), - (foreign_asset_id_minimum_balance + pool_liquidity).mul(2).into(), - )); - - assert_ok!(>::create_pool( - RuntimeHelper::::origin_of(pool_owner.clone()), - Box::new(native_asset.into()), - Box::new(foreign_asset_id_location.into()) - )); - - assert_ok!(>::add_liquidity( - RuntimeHelper::::origin_of(pool_owner.clone()), - Box::new(native_asset.into()), - Box::new(foreign_asset_id_location.into()), - pool_liquidity.into(), - pool_liquidity.into(), - 1.into(), - 1.into(), - pool_owner, - )); + // prepare bridge config + let TestBridgingConfig { local_bridge_hub_location, .. } = prepare_configuration(); // Balances before assert_eq!( >::free_balance(&target_account), existential_deposit.clone() ); - assert_eq!( - >::free_balance(&block_author_account), - 0.into() - ); - assert_eq!( - >::free_balance(&staking_pot), - existential_deposit.clone() - ); // ForeignAssets balances before assert_eq!( @@ -473,27 +414,16 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< ), 0.into() ); - assert_eq!( - >::balance( - foreign_asset_id_location.into(), - &block_author_account - ), - 0.into() - ); - assert_eq!( - >::balance( - foreign_asset_id_location.into(), - &staking_pot - ), - 0.into() - ); + + // additional check before + additional_checks_before(); let foreign_asset_id_location_latest: Location = foreign_asset_id_location.try_into().unwrap(); let expected_assets = Assets::from(vec![Asset { id: AssetId(foreign_asset_id_location_latest.clone()), - fun: Fungible(transfered_foreign_asset_id_amount), + fun: Fungible(foreign_asset_id_amount_to_transfer), }]); let expected_beneficiary = Location::new( 0, @@ -510,7 +440,7 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< BuyExecution { fees: Asset { id: AssetId(foreign_asset_id_location_latest.clone()), - fun: Fungible(transfered_foreign_asset_id_amount), + fun: Fungible(foreign_asset_id_amount_to_transfer), }, weight_limit: Unlimited, }, @@ -544,19 +474,10 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< assert_ok!(outcome.ensure_complete()); // Balances after - // staking pot receives xcm fees in dot - assert!( - >::free_balance(&staking_pot) != - existential_deposit - ); assert_eq!( >::free_balance(&target_account), existential_deposit.clone() ); - assert_eq!( - >::free_balance(&block_author_account), - 0.into() - ); // ForeignAssets balances after assert!( @@ -565,20 +486,9 @@ pub fn receive_reserve_asset_deposited_from_different_consensus_works< &target_account ) > 0.into() ); - assert_eq!( - >::balance( - foreign_asset_id_location.into(), - &staking_pot - ), - 0.into() - ); - assert_eq!( - >::balance( - foreign_asset_id_location.into(), - &block_author_account - ), - 0.into() - ); + + // additional check after + additional_checks_after(); }) } @@ -602,7 +512,8 @@ pub fn report_bridge_status_from_xcm_bridge_router_works< + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + cumulus_pallet_xcmp_queue::Config - + pallet_xcm_bridge_hub_router::Config, + + pallet_xcm_bridge_hub_router::Config + + pallet_timestamp::Config, AllPalletsWithoutSystem: OnInitialize> + OnFinalize>, AccountIdOf: Into<[u8; 32]>, diff --git a/cumulus/parachains/runtimes/bridge-hubs/README.md b/cumulus/parachains/runtimes/bridge-hubs/README.md index cf617db730dd7faa19d932dbe6a0406b7a0c15f2..c858532295ddce7ad1fd9a57a19d752201b78abd 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/README.md +++ b/cumulus/parachains/runtimes/bridge-hubs/README.md @@ -89,20 +89,18 @@ cp target/release/polkadot-parachain ~/local_bridge_testing/bin/polkadot-paracha cd # Rococo + BridgeHubRococo + AssetHub for Rococo (mirroring Kusama) -POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ -POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ -POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_ROCOCO=~/local_bridge_testing/bin/polkadot-parachain-asset-hub \ - ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./cumulus/zombienet/bridge-hubs/bridge_hub_rococo_local_network.toml +POLKADOT_BINARY=~/local_bridge_testing/bin/polkadot \ +POLKADOT_PARACHAIN_BINARY=~/local_bridge_testing/bin/polkadot-parachain \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./bridges/testing/environments/rococo-westend/bridge_hub_rococo_local_network.toml ``` ``` cd # Westend + BridgeHubWestend + AssetHub for Westend (mirroring Polkadot) -POLKADOT_BINARY_PATH=~/local_bridge_testing/bin/polkadot \ -POLKADOT_PARACHAIN_BINARY_PATH=~/local_bridge_testing/bin/polkadot-parachain \ -POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND=~/local_bridge_testing/bin/polkadot-parachain-asset-hub \ - ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./cumulus/zombienet/bridge-hubs/bridge_hub_westend_local_network.toml +POLKADOT_BINARY=~/local_bridge_testing/bin/polkadot \ +POLKADOT_PARACHAIN_BINARY=~/local_bridge_testing/bin/polkadot-parachain \ + ~/local_bridge_testing/bin/zombienet-linux --provider native spawn ./bridges/testing/environments/rococo-westend/bridge_hub_westend_local_network.toml ``` ### Init bridge and run relayer between BridgeHubRococo and BridgeHubWestend @@ -114,7 +112,7 @@ POLKADOT_PARACHAIN_BINARY_PATH_FOR_ASSET_HUB_WESTEND=~/local_bridge_testing/bin/ ``` cd -./cumulus/scripts/bridges_rococo_westend.sh run-relay +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh run-relay ``` **Check relay-chain headers relaying:** @@ -137,10 +135,10 @@ This initialization does several things: ``` cd -./cumulus/scripts/bridges_rococo_westend.sh init-asset-hub-rococo-local -./cumulus/scripts/bridges_rococo_westend.sh init-bridge-hub-rococo-local -./cumulus/scripts/bridges_rococo_westend.sh init-asset-hub-westend-local -./cumulus/scripts/bridges_rococo_westend.sh init-bridge-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-asset-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-bridge-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-asset-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh init-bridge-hub-westend-local ``` ### Send messages - transfer asset over bridge (ROCs/WNDs) @@ -150,13 +148,13 @@ Do reserve-backed transfers: cd # ROCs from Rococo's Asset Hub to Westend's. -./cumulus/scripts/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-rococo-local ``` ``` cd # WNDs from Westend's Asset Hub to Rococo's. -./cumulus/scripts/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh reserve-transfer-assets-from-asset-hub-westend-local ``` - open explorers: (see zombienets) @@ -171,13 +169,13 @@ Do reserve withdraw transfers: (when previous is finished) cd # wrappedWNDs from Rococo's Asset Hub to Westend's. -./cumulus/scripts/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-rococo-local ``` ``` cd # wrappedROCs from Westend's Asset Hub to Rococo's. -./cumulus/scripts/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh withdraw-reserve-assets-from-asset-hub-westend-local ``` ### Claim relayer's rewards on BridgeHubRococo and BridgeHubWestend @@ -190,10 +188,10 @@ cd cd # Claim rewards on BridgeHubWestend: -./cumulus/scripts/bridges_rococo_westend.sh claim-rewards-bridge-hub-rococo-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh claim-rewards-bridge-hub-rococo-local # Claim rewards on BridgeHubWestend: -./cumulus/scripts/bridges_rococo_westend.sh claim-rewards-bridge-hub-westend-local +./bridges/testing/environments/rococo-westend/bridges_rococo_westend.sh claim-rewards-bridge-hub-westend-local ``` - open explorers: (see zombienets) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 3e2dadd11ca060dba83e1cd7ab5111e85b7c4283..7a1951fd24bd2d74ae722eef31b81dfdb28c8d9b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -17,11 +17,11 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = [ "derive", ] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -76,6 +76,7 @@ cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = fals cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = [ "bridging", ] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -106,16 +107,16 @@ pallet-xcm-bridge-hub = { path = "../../../../../bridges/modules/xcm-bridge-hub" bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", default-features = false } # Ethereum Bridge (Snowbridge) -snowbridge-beacon-primitives = { path = "../../../../../bridges/snowbridge/parachain/primitives/beacon", default-features = false } -snowbridge-pallet-system = { path = "../../../../../bridges/snowbridge/parachain/pallets/system", default-features = false } -snowbridge-system-runtime-api = { path = "../../../../../bridges/snowbridge/parachain/pallets/system/runtime-api", default-features = false } -snowbridge-core = { path = "../../../../../bridges/snowbridge/parachain/primitives/core", default-features = false } -snowbridge-pallet-ethereum-client = { path = "../../../../../bridges/snowbridge/parachain/pallets/ethereum-client", default-features = false } -snowbridge-pallet-inbound-queue = { path = "../../../../../bridges/snowbridge/parachain/pallets/inbound-queue", default-features = false } -snowbridge-pallet-outbound-queue = { path = "../../../../../bridges/snowbridge/parachain/pallets/outbound-queue", default-features = false } -snowbridge-outbound-queue-runtime-api = { path = "../../../../../bridges/snowbridge/parachain/pallets/outbound-queue/runtime-api", default-features = false } -snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/parachain/primitives/router", default-features = false } -snowbridge-runtime-common = { path = "../../../../../bridges/snowbridge/parachain/runtime/runtime-common", default-features = false } +snowbridge-beacon-primitives = { path = "../../../../../bridges/snowbridge/primitives/beacon", default-features = false } +snowbridge-pallet-system = { path = "../../../../../bridges/snowbridge/pallets/system", default-features = false } +snowbridge-system-runtime-api = { path = "../../../../../bridges/snowbridge/pallets/system/runtime-api", default-features = false } +snowbridge-core = { path = "../../../../../bridges/snowbridge/primitives/core", default-features = false } +snowbridge-pallet-ethereum-client = { path = "../../../../../bridges/snowbridge/pallets/ethereum-client", default-features = false } +snowbridge-pallet-inbound-queue = { path = "../../../../../bridges/snowbridge/pallets/inbound-queue", default-features = false } +snowbridge-pallet-outbound-queue = { path = "../../../../../bridges/snowbridge/pallets/outbound-queue", default-features = false } +snowbridge-outbound-queue-runtime-api = { path = "../../../../../bridges/snowbridge/pallets/outbound-queue/runtime-api", default-features = false } +snowbridge-router-primitives = { path = "../../../../../bridges/snowbridge/primitives/router", default-features = false } +snowbridge-runtime-common = { path = "../../../../../bridges/snowbridge/runtime/runtime-common", default-features = false } bridge-hub-common = { path = "../common", default-features = false } @@ -126,7 +127,7 @@ bridge-runtime-common = { path = "../../../../../bridges/bin/runtime-common", fe "integrity-test", ] } sp-keyring = { path = "../../../../../substrate/primitives/keyring" } -snowbridge-runtime-test-common = { path = "../../../../../bridges/snowbridge/parachain/runtime/test-common" } +snowbridge-runtime-test-common = { path = "../../../../../bridges/snowbridge/runtime/test-common" } [features] default = ["std"] @@ -153,6 +154,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index 3a626e2cba7c1518fa7fccc118bf536617c358c6..5d55d7afbacfdb22f6939c88e87eaf64321945ff 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -299,7 +299,7 @@ mod tests { >(AssertCompleteBridgeConstants { this_chain_constants: AssertChainConstants { block_length: bp_bridge_hub_rococo::BlockLength::get(), - block_weights: bp_bridge_hub_rococo::BlockWeights::get(), + block_weights: bp_bridge_hub_rococo::BlockWeightsForAsyncBacking::get(), }, messages_pallet_constants: AssertBridgeMessagesPalletConstants { max_unrewarded_relayers_in_bridged_confirmation_tx: diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 94d5772861c1f16424ac5faeacd257d61650f7e7..15cf045a99561a0c9bf0828e59e10f76d1916599 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -35,7 +35,7 @@ pub mod bridge_to_westend_config; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{ gwei, meth, outbound::Message, AgentId, AllowSiblingsOnly, PricingParameters, Rewards, @@ -69,9 +69,10 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; +use testnet_parachains_constants::rococo::{ + consensus::*, currency::*, fee::WeightToFee, snowbridge::INBOUND_QUEUE_PALLET_INDEX, time::*, +}; -#[cfg(feature = "runtime-benchmarks")] -use bp_runtime::Chain; use bp_runtime::HeaderId; use bridge_hub_common::{ message_queue::{NarrowOriginToSibling, ParaIdToSibling}, @@ -94,10 +95,7 @@ use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use parachains_common::{ impls::DealWithFees, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, -}; -use testnet_parachains_constants::rococo::{ - consensus::*, currency::*, fee::WeightToFee, snowbridge::INBOUND_QUEUE_PALLET_INDEX, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::prod_or_fast; @@ -152,6 +150,8 @@ pub type Migrations = ( ConstU32, ConstU32, >, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Migration to initialize storage versions for pallets added after genesis. @@ -204,7 +204,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bridge-hub-rococo"), impl_name: create_runtime_str!("bridge-hub-rococo"), authoring_version: 1, - spec_version: 1_006_001, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 4, @@ -279,6 +279,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -340,15 +343,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} parameter_types! { @@ -437,9 +442,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -725,7 +730,7 @@ construct_runtime!( // Message Queue. Importantly, is registered last so that messages are processed after // the `on_initialize` hooks of bridging pallets. - MessageQueue: pallet_message_queue = 250, + MessageQueue: pallet_message_queue = 175, } ); @@ -783,7 +788,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -791,6 +796,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION @@ -1095,6 +1109,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -1103,7 +1123,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between BH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), @@ -1282,7 +1302,7 @@ impl_runtime_apis! { impl BridgeMessagesConfig for Runtime { fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool { let bench_lane_id = >::bench_lane_id(); - let bridged_chain_id = bp_bridge_hub_westend::BridgeHubWestend::ID; + let bridged_chain_id = bridge_to_westend_config::BridgeHubWestendChainId::get(); pallet_bridge_relayers::Pallet::::relayer_reward( relayer, bp_relayers::RewardsAccountParams::new( diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs index b7d170707cfd42c66870fe249a10bcb3ecbb73d3..55c78477b5684987fe07eaaa87d45d8900e74661 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs @@ -54,16 +54,14 @@ use sp_runtime::traits::AccountIdConversion; use sp_std::marker::PhantomData; use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; use xcm::latest::prelude::*; -#[allow(deprecated)] use xcm_builder::{ deposit_or_burn_fee, AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, - CurrencyAdapter, DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, - FrameTransactionalProcessor, HandleFee, IsConcrete, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeToAccount, + DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, + FungibleAdapter, HandleFee, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeToAccount, }; use xcm_executor::{ traits::{FeeManager, FeeReason, FeeReason::Export, TransactAsset, WithOriginFilter}, @@ -96,8 +94,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -285,7 +282,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // BridgeHub does not recognize a reserve location for any asset. Users must teleport Native // token where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs index e14755c89d4d8ba2b103cf43d60ef4d96f2166a1..b9f43624b652f97ec1616664548baaadb5e4c05f 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -20,9 +20,9 @@ use bp_polkadot_core::Signature; use bridge_hub_rococo_runtime::{ bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages, bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages, - xcm_config::XcmConfig, BridgeRejectObsoleteHeadersAndMessages, Executive, - MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, SignedExtra, - UncheckedExtrinsic, + xcm_config::XcmConfig, AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, + Executive, MessageQueueServiceWeight, Runtime, RuntimeCall, RuntimeEvent, SessionKeys, + SignedExtra, UncheckedExtrinsic, }; use codec::{Decode, Encode}; use cumulus_primitives_core::XcmError::{FailedToTransactAsset, NotHoldingFees}; @@ -102,7 +102,7 @@ pub fn transfer_token_to_ethereum_insufficient_fund() { H160::random(), H160::random(), DefaultBridgeHubEthereumBaseFee::get(), - FailedToTransactAsset("InsufficientBalance"), + FailedToTransactAsset("Funds are unavailable"), ) } @@ -135,6 +135,32 @@ fn ethereum_to_polkadot_message_extrinsics_work() { ); } +/// Tests that the digest items are as expected when a Ethereum Outbound message is received. +/// If the MessageQueue pallet is configured before (i.e. the MessageQueue pallet is listed before +/// the EthereumOutboundQueue in the construct_runtime macro) the EthereumOutboundQueue, this test +/// will fail. +#[test] +pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works() { + snowbridge_runtime_test_common::ethereum_outbound_queue_processes_messages_before_message_queue_works::< + Runtime, + XcmConfig, + AllPalletsWithoutSystem, + >( + collator_session_keys(), + 1013, + 1000, + H160::random(), + H160::random(), + DefaultBridgeHubEthereumBaseFee::get(), + Box::new(|runtime_event_encoded: Vec| { + match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) { + Ok(RuntimeEvent::EthereumOutboundQueue(event)) => Some(event), + _ => None, + } + }), + ) +} + fn construct_extrinsic( sender: sp_keyring::AccountKeyring, call: RuntimeCall, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index a68d0e6a10692107d61c55cab82989cdc8b8e2fe..f11954cf165fdae2ee36377456b2bc4b2d6755a6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -27,18 +27,16 @@ use bridge_hub_rococo_runtime::{ use bridge_hub_test_utils::SlotDurations; use codec::{Decode, Encode}; use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8}; -use parachains_common::{AccountId, AuraId, Balance, SLOT_DURATION}; +use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_core::ChannelId; use sp_consensus_aura::SlotDuration; use sp_core::H160; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, - AccountId32, -}; -use testnet_parachains_constants::rococo::{ - consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, fee::WeightToFee, + AccountId32, Perbill, }; +use testnet_parachains_constants::rococo::{consensus::*, fee::WeightToFee}; use xcm::latest::prelude::*; parameter_types! { @@ -400,53 +398,61 @@ mod bridge_hub_westend_tests { #[test] pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { - let estimated = bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< - Runtime, - XcmConfig, - WeightToFee, - >(); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs::get(), + || { + bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< + Runtime, + XcmConfig, + WeightToFee, + >() + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `ExportMessage` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_delivery_transaction() { - let estimated = from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message delivery` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { - let estimated = - from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message confirmation` for runtime: {:?}", + ::Version::get() + ), + ) } } @@ -594,55 +600,43 @@ mod bridge_hub_bulletin_tests { ); } - #[test] - pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { - let estimated = bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< - Runtime, - XcmConfig, - WeightToFee, - >(); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseXcmFeeInRocs` value", - estimated, - max_expected - ); - } - #[test] pub fn can_calculate_fee_for_complex_message_delivery_transaction() { - let estimated = - from_grandpa_chain::can_calculate_fee_for_complex_message_delivery_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseDeliveryFeeInRocs::get(), + || { + from_grandpa_chain::can_calculate_fee_for_complex_message_delivery_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + None, /* we don't want lowering according to the Bulletin setup, because + * `from_grandpa_chain` is cheaper then `from_parachain_chain` */ + &format!( + "Estimate fee for `single message delivery` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { - let estimated = - from_grandpa_chain::can_calculate_fee_for_complex_message_confirmation_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs", + bp_bridge_hub_rococo::BridgeHubRococoBaseConfirmationFeeInRocs::get(), + || { + from_grandpa_chain::can_calculate_fee_for_complex_message_confirmation_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + None, /* we don't want lowering according to the Bulletin setup, because + * `from_grandpa_chain` is cheaper then `from_parachain_chain` */ + &format!( + "Estimate fee for `single message confirmation` for runtime: {:?}", + ::Version::get() + ), + ) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index e6d3db1ba49a2f394a9bc2d48b016c0ddbfbbedd..8623f7cb366ecf3930b25563d6a912e53240d999 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -15,9 +15,9 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -68,6 +68,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false, features = ["bridging"] } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -125,6 +126,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index 4567494f825062fca10013bec2e37449e4532df9..bce722aa5f87d006af0ec71429d6c84eeab4972d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -334,7 +334,7 @@ mod tests { >(AssertCompleteBridgeConstants { this_chain_constants: AssertChainConstants { block_length: bp_bridge_hub_westend::BlockLength::get(), - block_weights: bp_bridge_hub_westend::BlockWeights::get(), + block_weights: bp_bridge_hub_westend::BlockWeightsForAsyncBacking::get(), }, messages_pallet_constants: AssertBridgeMessagesPalletConstants { max_unrewarded_relayers_in_bridged_confirmation_tx: diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 5637c3552f58b9f4eea8195d77b8af71e979b9ae..bd42a33370dc81eed36dce2741e4fb6dc01bf1f3 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -32,7 +32,7 @@ pub mod bridge_to_rococo_config; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::ParaId; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; @@ -69,8 +69,6 @@ pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; use xcm_config::{XcmOriginToTransactDispatchOrigin, XcmRouter}; -#[cfg(feature = "runtime-benchmarks")] -use bp_runtime::Chain; use bp_runtime::HeaderId; #[cfg(any(feature = "std", test))] @@ -83,9 +81,9 @@ use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use parachains_common::{ impls::DealWithFees, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; -use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; /// The address format for describing accounts. pub type Address = MultiAddress; @@ -124,6 +122,8 @@ pub type Migrations = ( InitStorageVersions, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Migration to initialize storage versions for pallets added after genesis. @@ -176,7 +176,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("bridge-hub-westend"), impl_name: create_runtime_str!("bridge-hub-westend"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 4, @@ -251,6 +251,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -312,15 +315,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} parameter_types! { @@ -401,9 +406,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -517,6 +522,7 @@ mod benches { [pallet_utility, Utility] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] // XCM [pallet_xcm, PalletXcmExtrinsicsBenchmark::] @@ -534,7 +540,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -542,6 +548,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION @@ -791,6 +806,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -799,7 +820,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between BH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), @@ -975,7 +996,7 @@ impl_runtime_apis! { impl BridgeMessagesConfig for Runtime { fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool { let bench_lane_id = >::bench_lane_id(); - let bridged_chain_id = bp_bridge_hub_rococo::BridgeHubRococo::ID; + let bridged_chain_id = bridge_to_rococo_config::BridgeHubRococoChainId::get(); pallet_bridge_relayers::Pallet::::relayer_reward( relayer, bp_relayers::RewardsAccountParams::new( diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index d2f0f187a20dd60bcba1eedb626f870ab61302ff..e18df6feda82754d83db711bb2c6ea813c387fca 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -38,16 +38,15 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, - XcmFeeManagerFromComponents, XcmFeeToAccount, + DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, + WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -76,8 +75,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -233,7 +231,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // BridgeHub does not recognize a reserve location for any asset. Users must teleport Native // token where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 22ade91334ca128ab7957cb35a193eb970f95489..149a3bbeb75d82ba7c35641428c7677d18297f8c 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -33,16 +33,14 @@ use bridge_to_rococo_config::{ }; use codec::{Decode, Encode}; use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8}; -use parachains_common::{AccountId, AuraId, Balance, SLOT_DURATION}; +use parachains_common::{AccountId, AuraId, Balance}; use sp_consensus_aura::SlotDuration; use sp_keyring::AccountKeyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, - AccountId32, -}; -use testnet_parachains_constants::westend::{ - consensus::RELAY_CHAIN_SLOT_DURATION_MILLIS, fee::WeightToFee, + AccountId32, Perbill, }; +use testnet_parachains_constants::westend::{consensus::*, fee::WeightToFee}; use xcm::latest::prelude::*; // Para id of sibling chain used in tests. @@ -295,50 +293,59 @@ pub fn complex_relay_extrinsic_works() { #[test] pub fn can_calculate_weight_for_paid_export_message_with_reserve_transfer() { - let estimated = bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds", + bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds::get(), + || { + bridge_hub_test_utils::test_cases::can_calculate_weight_for_paid_export_message_with_reserve_transfer::< Runtime, XcmConfig, WeightToFee, - >(); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_westend::BridgeHubWestendBaseXcmFeeInWnds` value", - estimated, - max_expected - ); + >() + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `ExportMessage` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_delivery_transaction() { - let estimated = from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds", + bp_bridge_hub_westend::BridgeHubWestendBaseDeliveryFeeInWnds::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_delivery_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message delivery` for runtime: {:?}", + ::Version::get() + ), + ) } #[test] pub fn can_calculate_fee_for_complex_message_confirmation_transaction() { - let estimated = from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< - RuntimeTestsAdapter, - >(collator_session_keys(), construct_and_estimate_extrinsic_fee); - - // check if estimated value is sane - let max_expected = bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get(); - assert!( - estimated <= max_expected, - "calculated: {:?}, max_expected: {:?}, please adjust `bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds` value", - estimated, - max_expected - ); + bridge_hub_test_utils::check_sane_fees_values( + "bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds", + bp_bridge_hub_westend::BridgeHubWestendBaseConfirmationFeeInWnds::get(), + || { + from_parachain::can_calculate_fee_for_complex_message_confirmation_transaction::< + RuntimeTestsAdapter, + >(collator_session_keys(), construct_and_estimate_extrinsic_fee) + }, + Perbill::from_percent(33), + Some(-33), + &format!( + "Estimate fee for `single message confirmation` for runtime: {:?}", + ::Version::get() + ), + ) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml index cb873ad4bc36c3002beef2d5e6cf0658d46e3ffa..a4dcd19dc9e8675599eaad9c2d340eca5874e63b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml @@ -16,7 +16,7 @@ sp-runtime = { path = "../../../../../substrate/primitives/runtime", default-fea cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } xcm = { package = "staging-xcm", path = "../../../../../polkadot/xcm", default-features = false } pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } -snowbridge-core = { path = "../../../../../bridges/snowbridge/parachain/primitives/core", default-features = false } +snowbridge-core = { path = "../../../../../bridges/snowbridge/primitives/core", default-features = false } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index f2cf60354adc69e33f6cc19e86d2173f3e30c511..5f2a6e050d83c3db662f8ff4896d32dc8a28fde3 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } impl-trait-for-tuples = "0.2" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } # Substrate frame-support = { path = "../../../../../substrate/frame/support", default-features = false } @@ -25,6 +25,7 @@ sp-std = { path = "../../../../../substrate/primitives/std", default-features = sp-tracing = { path = "../../../../../substrate/primitives/tracing" } pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } pallet-utility = { path = "../../../../../substrate/frame/utility", default-features = false } +pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } # Cumulus asset-test-utils = { path = "../../assets/test-utils" } @@ -73,6 +74,7 @@ std = [ "pallet-bridge-messages/std", "pallet-bridge-parachains/std", "pallet-bridge-relayers/std", + "pallet-timestamp/std", "pallet-utility/std", "parachains-common/std", "parachains-runtimes-test-utils/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs index 445f001f1a4c111920fc513be95f88d73a25a634..1874f38de2df17e85c1f49723271d090e962eb70 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/lib.rs @@ -21,3 +21,60 @@ pub mod test_data; pub use bp_test_utils::test_header; pub use parachains_runtimes_test_utils::*; +use sp_runtime::Perbill; + +/// A helper function for comparing the actual value of a fee constant with its estimated value. The +/// estimated value can be overestimated (`overestimate_in_percent`), and if the difference to the +/// actual value is below `margin_overestimate_diff_in_percent_for_lowering`, we should lower the +/// actual value. +pub fn check_sane_fees_values( + const_name: &str, + actual: u128, + calculate_estimated_fee: fn() -> u128, + overestimate_in_percent: Perbill, + margin_overestimate_diff_in_percent_for_lowering: Option, + label: &str, +) { + let estimated = calculate_estimated_fee(); + let estimated_plus_overestimate = estimated + (overestimate_in_percent * estimated); + let diff_to_estimated = diff_as_percent(actual, estimated); + let diff_to_estimated_plus_overestimate = diff_as_percent(actual, estimated_plus_overestimate); + + sp_tracing::try_init_simple(); + log::error!( + target: "bridges::estimate", + "{label}:\nconstant: {const_name}\n[+] actual: {actual}\n[+] estimated: {estimated} ({diff_to_estimated:.2?})\n[+] estimated(+33%): {estimated_plus_overestimate} ({diff_to_estimated_plus_overestimate:.2?})", + ); + + // check if estimated value is sane + assert!( + estimated <= actual, + "estimated: {estimated}, actual: {actual}, please adjust `{const_name}` to the value: {estimated_plus_overestimate}", + ); + assert!( + estimated_plus_overestimate <= actual, + "estimated_plus_overestimate: {estimated_plus_overestimate}, actual: {actual}, please adjust `{const_name}` to the value: {estimated_plus_overestimate}", + ); + + if let Some(margin_overestimate_diff_in_percent_for_lowering) = + margin_overestimate_diff_in_percent_for_lowering + { + assert!( + diff_to_estimated_plus_overestimate > margin_overestimate_diff_in_percent_for_lowering as f64, + "diff_to_estimated_plus_overestimate: {diff_to_estimated_plus_overestimate:.2}, overestimate_diff_in_percent_for_lowering: {margin_overestimate_diff_in_percent_for_lowering}, please adjust `{const_name}` to the value: {estimated_plus_overestimate}", + ); + } +} + +pub fn diff_as_percent(left: u128, right: u128) -> f64 { + let left = left as f64; + let right = right as f64; + ((left - right).abs() / left) * 100f64 * (if left >= right { -1 } else { 1 }) as f64 +} + +#[test] +fn diff_as_percent_works() { + assert_eq!(-20_f64, diff_as_percent(100, 80)); + assert_eq!(25_f64, diff_as_percent(80, 100)); + assert_eq!(33_f64, diff_as_percent(13351000000, 17756830000)); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs index acf0f2c71620a56af93fa23338fd498af5ab19d9..8aaaa4f59d7884ff211855a925638317a3b722ea 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs @@ -36,7 +36,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::XcmAsPlainPayload, }; -use frame_support::traits::{Get, OnFinalize, OnInitialize}; +use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, @@ -358,16 +358,8 @@ where message_proof, helpers::relayer_id_at_bridged_chain::(), ); - let estimated_fee = compute_extrinsic_fee(batch); - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for single message delivery for runtime: {:?}", - estimated_fee, - ::Version::get(), - ); - - estimated_fee + compute_extrinsic_fee(batch) }) } @@ -427,15 +419,7 @@ where message_delivery_proof, unrewarded_relayers, ); - let estimated_fee = compute_extrinsic_fee(batch); - - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for single message confirmation for runtime: {:?}", - estimated_fee, - ::Version::get(), - ); - estimated_fee + compute_extrinsic_fee(batch) }) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs index 8a86c5bb72f5885d69612839ea32197280f62f6e..72ec0718acf7759aedb02e91356fea73ee73e7e7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs @@ -37,7 +37,7 @@ use bridge_runtime_common::{ }, messages_xcm_extension::XcmAsPlainPayload, }; -use frame_support::traits::{Get, OnFinalize, OnInitialize}; +use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::pallet_prelude::BlockNumberFor; use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, @@ -446,16 +446,8 @@ where message_proof, helpers::relayer_id_at_bridged_chain::(), ); - let estimated_fee = compute_extrinsic_fee(batch); - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for single message delivery for runtime: {:?}", - estimated_fee, - ::Version::get(), - ); - - estimated_fee + compute_extrinsic_fee(batch) }) } @@ -531,15 +523,7 @@ where message_delivery_proof, unrewarded_relayers, ); - let estimated_fee = compute_extrinsic_fee(batch); - - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for single message confirmation for runtime: {:?}", - estimated_fee, - ::Version::get(), - ); - estimated_fee + compute_extrinsic_fee(batch) }) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs index 4f634c184aa84faa6466102ef5fee30f254bad43..2b48f2e3d515f625532d9c5f50fabadb9a89517a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs @@ -197,7 +197,9 @@ where pub(crate) fn initialize_bridge_grandpa_pallet( init_data: bp_header_chain::InitializationData>, ) where - Runtime: BridgeGrandpaConfig, + Runtime: BridgeGrandpaConfig + + cumulus_pallet_parachain_system::Config + + pallet_timestamp::Config, { pallet_bridge_grandpa::Pallet::::initialize( RuntimeHelper::::root_origin(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs index 8d08915bf1fac1d29726b61cb2ddae4f8dd8e739..bc1c7ec5e032c08fb36b3005d4abcaf24bb43ff4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs @@ -641,13 +641,5 @@ where let estimated_fee = WeightToFee::weight_to_fee(&weight); assert!(estimated_fee > BalanceOf::::zero()); - sp_tracing::try_init_simple(); - log::error!( - target: "bridges::estimate", - "Estimate fee: {:?} for `ExportMessage` for runtime: {:?}", - estimated_fee, - Runtime::Version::get(), - ); - estimated_fee.into() } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 21f4d2a1970c49a04874d2a7b215ac095e96f3ee..ed264f28c26e4d48bca416f1e6bea0f0049aa334 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate @@ -74,6 +74,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -169,6 +170,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs index d9042f2a5568d402146196c7fb5a745f7213cb6b..0c9f428c1396bede97a67002d0554d98d62dbc39 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs @@ -40,7 +40,7 @@ use origins::pallet_origins::{ EnsureAmbassadorsVoice, EnsureAmbassadorsVoiceFrom, EnsureHeadAmbassadorsVoice, Origin, }; use sp_core::ConstU128; -use sp_runtime::traits::{CheckedReduceBy, ConstU16, ConvertToValue, Replace}; +use sp_runtime::traits::{CheckedReduceBy, ConstU16, ConvertToValue, Replace, ReplaceWithDefault}; use xcm::prelude::*; use xcm_builder::{AliasesIntoAccountId32, PayOverXcm}; @@ -108,8 +108,10 @@ pub type ExchangeOrigin = EitherOf for Runtime { type WeightInfo = weights::pallet_ranked_collective_ambassador_collective::WeightInfo; type RuntimeEvent = RuntimeEvent; + type AddOrigin = MapSuccess>; type PromoteOrigin = PromoteOrigin; type DemoteOrigin = DemoteOrigin; + type RemoveOrigin = Self::DemoteOrigin; type ExchangeOrigin = ExchangeOrigin; type Polls = AmbassadorReferenda; type MinRankOfClass = sp_runtime::traits::Identity; diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs index 6632d511f7c3cf42aab7e4e98085b6cddf15b717..3816d2ed848ed51740283ffea31e9f7e53c01f1a 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs @@ -113,12 +113,17 @@ impl pallet_ranked_collective::Config for Runtime type WeightInfo = weights::pallet_ranked_collective_fellowship_collective::WeightInfo; type RuntimeEvent = RuntimeEvent; - #[cfg(not(feature = "runtime-benchmarks"))] // Promotions and the induction of new members are serviced by `FellowshipCore` pallet instance. - type PromoteOrigin = frame_system::EnsureNever; + #[cfg(not(feature = "runtime-benchmarks"))] + type AddOrigin = frame_system::EnsureNever<()>; #[cfg(feature = "runtime-benchmarks")] + type AddOrigin = frame_system::EnsureRoot; + // The maximum value of `u16` set as a success value for the root to ensure the benchmarks will // pass. + #[cfg(not(feature = "runtime-benchmarks"))] + type PromoteOrigin = frame_system::EnsureNever; + #[cfg(feature = "runtime-benchmarks")] type PromoteOrigin = EnsureRootWithSuccess>; // Demotion is by any of: @@ -127,6 +132,7 @@ impl pallet_ranked_collective::Config for Runtime // // The maximum value of `u16` set as a success value for the root to ensure the benchmarks will // pass. + type RemoveOrigin = Self::DemoteOrigin; type DemoteOrigin = EitherOf< EnsureRootWithSuccess>, MapSuccess< diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 3f488d04ef0b5df8c3e703077c07a10aeceeb8b9..36eb0e07f87c16704ab27745c2f19efc113884e3 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -44,7 +44,7 @@ pub mod xcm_config; pub mod fellowship; pub use ambassador::pallet_ambassador_origins; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use fellowship::{pallet_fellowship_origins, Fellows}; use impls::{AllianceProposalProvider, EqualOrGreatestRootCmp}; use sp_api::impl_runtime_apis; @@ -84,12 +84,11 @@ use parachains_common::{ impls::{DealWithFees, ToParentTreasury}, message_queue::*, AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, DAYS, HOURS, MAXIMUM_BLOCK_WEIGHT, MINUTES, NORMAL_DISPATCH_RATIO, - SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use sp_runtime::RuntimeDebug; use testnet_parachains_constants::westend::{ - account::*, consensus::*, currency::*, fee::WeightToFee, + account::*, consensus::*, currency::*, fee::WeightToFee, time::*, }; use xcm_config::{ GovernanceLocation, LocationToAccountId, TreasurerBodyId, XcmOriginToTransactDispatchOrigin, @@ -118,7 +117,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("collectives-westend"), impl_name: create_runtime_str!("collectives-westend"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 5, @@ -185,6 +184,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -387,15 +389,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl parachain_info::Config for Runtime {} parameter_types! { @@ -478,9 +482,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -723,6 +727,8 @@ type Migrations = ( pallet_collator_selection::migration::v1::MigrateToV1, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); /// Executive: handles dispatch to the various modules. @@ -771,7 +777,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -779,6 +785,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION @@ -971,8 +986,21 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + xcm_config::WndLocation::get(), + ExistentialDeposit::get() + ).into()); + } + use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -981,7 +1009,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between Collectives and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }.into(), Parent.into(), diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs index cf5610df66574a38a388b7e36a56f87e41ff59c2..42e37b967e4c88acccd13c8cb71bd22b1bc2d3dd 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_scheduler.rs @@ -1,42 +1,41 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// 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. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_scheduler -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=collectives-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,8 +54,8 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `31` // Estimated: `1489` - // Minimum execution time: 3_441_000 picoseconds. - Weight::from_parts(3_604_000, 0) + // Minimum execution time: 2_475_000 picoseconds. + Weight::from_parts(2_644_000, 0) .saturating_add(Weight::from_parts(0, 1489)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -68,11 +67,11 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `77 + s * (177 ±0)` // Estimated: `159279` - // Minimum execution time: 2_879_000 picoseconds. - Weight::from_parts(2_963_000, 0) + // Minimum execution time: 2_898_000 picoseconds. + Weight::from_parts(1_532_342, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 3_764 - .saturating_add(Weight::from_parts(909_557, 0).saturating_mul(s.into())) + // Standard Error: 4_736 + .saturating_add(Weight::from_parts(412_374, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -80,25 +79,27 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_172_000 picoseconds. - Weight::from_parts(5_294_000, 0) + // Minimum execution time: 3_171_000 picoseconds. + Weight::from_parts(3_349_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Preimage::PreimageFor` (r:1 w:1) /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) - /// Storage: `Preimage::StatusFor` (r:1 w:1) + /// Storage: `Preimage::StatusFor` (r:1 w:0) /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `213 + s * (1 ±0)` - // Estimated: `3678 + s * (1 ±0)` - // Minimum execution time: 19_704_000 picoseconds. - Weight::from_parts(19_903_000, 0) - .saturating_add(Weight::from_parts(0, 3678)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_394, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Measured: `246 + s * (1 ±0)` + // Estimated: `3711 + s * (1 ±0)` + // Minimum execution time: 17_329_000 picoseconds. + Weight::from_parts(17_604_000, 0) + .saturating_add(Weight::from_parts(0, 3711)) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_256, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } @@ -108,8 +109,8 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_359_000 picoseconds. - Weight::from_parts(6_599_000, 0) + // Minimum execution time: 4_503_000 picoseconds. + Weight::from_parts(4_677_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -117,24 +118,24 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_217_000 picoseconds. - Weight::from_parts(5_333_000, 0) + // Minimum execution time: 3_145_000 picoseconds. + Weight::from_parts(3_252_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_406_000 picoseconds. - Weight::from_parts(2_541_000, 0) + // Minimum execution time: 1_804_000 picoseconds. + Weight::from_parts(1_891_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_370_000 picoseconds. - Weight::from_parts(2_561_000, 0) + // Minimum execution time: 1_706_000 picoseconds. + Weight::from_parts(1_776_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `Scheduler::Agenda` (r:1 w:1) @@ -144,11 +145,11 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `77 + s * (177 ±0)` // Estimated: `159279` - // Minimum execution time: 11_784_000 picoseconds. - Weight::from_parts(5_574_404, 0) + // Minimum execution time: 8_629_000 picoseconds. + Weight::from_parts(6_707_232, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 7_217 - .saturating_add(Weight::from_parts(1_035_248, 0).saturating_mul(s.into())) + // Standard Error: 5_580 + .saturating_add(Weight::from_parts(471_827, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -161,11 +162,11 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `77 + s * (177 ±0)` // Estimated: `159279` - // Minimum execution time: 16_373_000 picoseconds. - Weight::from_parts(3_088_135, 0) + // Minimum execution time: 12_675_000 picoseconds. + Weight::from_parts(7_791_682, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 7_095 - .saturating_add(Weight::from_parts(1_745_270, 0).saturating_mul(s.into())) + // Standard Error: 5_381 + .saturating_add(Weight::from_parts(653_023, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -178,11 +179,11 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `468 + s * (179 ±0)` // Estimated: `159279` - // Minimum execution time: 14_822_000 picoseconds. - Weight::from_parts(9_591_402, 0) + // Minimum execution time: 11_908_000 picoseconds. + Weight::from_parts(11_833_059, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 7_151 - .saturating_add(Weight::from_parts(1_058_408, 0).saturating_mul(s.into())) + // Standard Error: 5_662 + .saturating_add(Weight::from_parts(482_816, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -195,12 +196,91 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `509 + s * (179 ±0)` // Estimated: `159279` - // Minimum execution time: 18_541_000 picoseconds. - Weight::from_parts(6_522_239, 0) + // Minimum execution time: 15_506_000 picoseconds. + Weight::from_parts(11_372_975, 0) .saturating_add(Weight::from_parts(0, 159279)) - // Standard Error: 8_349 - .saturating_add(Weight::from_parts(1_760_431, 0).saturating_mul(s.into())) + // Standard Error: 5_765 + .saturating_add(Weight::from_parts(656_322, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 200]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `159279` + // Minimum execution time: 14_069_000 picoseconds. + Weight::from_parts(14_868_345, 0) + .saturating_add(Weight::from_parts(0, 159279)) + // Standard Error: 425 + .saturating_add(Weight::from_parts(33_468, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `77 + s * (177 ±0)` + // Estimated: `159279` + // Minimum execution time: 7_550_000 picoseconds. + Weight::from_parts(6_735_955, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `513 + s * (179 ±0)` + // Estimated: `159279` + // Minimum execution time: 11_017_000 picoseconds. + Weight::from_parts(11_749_385, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `77 + s * (177 ±0)` + // Estimated: `159279` + // Minimum execution time: 7_550_000 picoseconds. + Weight::from_parts(6_735_955, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(155814), added: 158289, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `513 + s * (179 ±0)` + // Estimated: `159279` + // Minimum execution time: 11_017_000 picoseconds. + Weight::from_parts(11_749_385, 0) + .saturating_add(Weight::from_parts(0, 159279)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index ad19521898dd317b3d2df789a7553d062a1acffc..cc25cbda0a4276c0c8956cf94b590998f4d3fac9 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -36,13 +36,11 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use westend_runtime_constants::xcm as xcm_constants; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, - LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, + DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, + IsConcrete, LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, @@ -85,9 +83,8 @@ pub type LocationToAccountId = ( AccountId32Aliases, ); -/// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +/// Means for transacting the native currency on this chain.#[allow(deprecated)] +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -262,7 +259,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // Collectives does not recognize a reserve location for any asset. Users must teleport WND // where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/constants/Cargo.toml b/cumulus/parachains/runtimes/constants/Cargo.toml index b9aea7ec48d6652f49734a4428c663d0169cf9fd..561e8276b5f0543001e10fd21345ea5d3a65fee5 100644 --- a/cumulus/parachains/runtimes/constants/Cargo.toml +++ b/cumulus/parachains/runtimes/constants/Cargo.toml @@ -25,9 +25,13 @@ rococo-runtime-constants = { path = "../../../../polkadot/runtime/rococo/constan westend-runtime-constants = { path = "../../../../polkadot/runtime/westend/constants", default-features = false, optional = true } xcm = { package = "staging-xcm", path = "../../../../polkadot/xcm", default-features = false } +# Cumulus +cumulus-primitives-core = { path = "../../../primitives/core", default-features = false } + [features] default = ["std"] std = [ + "cumulus-primitives-core/std", "frame-support/std", "polkadot-core-primitives/std", "rococo-runtime-constants?/std", diff --git a/cumulus/parachains/runtimes/constants/src/rococo.rs b/cumulus/parachains/runtimes/constants/src/rococo.rs index 9ab57f0a6c89aba445b694ac17561b2c9e8f86a9..d10b5e7d3af4368d9bd50664b8be029a3f812795 100644 --- a/cumulus/parachains/runtimes/constants/src/rococo.rs +++ b/cumulus/parachains/runtimes/constants/src/rococo.rs @@ -108,14 +108,42 @@ pub mod fee { /// Consensus-related. pub mod consensus { + use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; + /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included /// into the relay chain. - pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; + pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; /// How many parachain blocks are processed by the relay chain per parent. Limits the /// number of blocks authored per slot. pub const BLOCK_PROCESSING_VELOCITY: u32 = 1; /// Relay chain slot duration, in milliseconds. pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; + + /// We allow for 2 seconds of compute with a 6 second average block. + pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( + WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, + ); + + /// This determines the average expected block time that we are targeting. + /// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. + /// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked + /// up by `pallet_aura` to implement `fn slot_duration()`. + /// + /// Change this to adjust the block time. + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; +} + +/// Time-related +pub mod time { + use polkadot_core_primitives::BlockNumber; + + // Time is measured by number of blocks. + pub const MINUTES: BlockNumber = + 60_000 / (super::consensus::MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; } pub mod snowbridge { diff --git a/cumulus/parachains/runtimes/constants/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs index 2bd4d18a15eba8fc04f0505439d55cb56062f67a..607d91e8808d7d9c0aacaa8d1f4056ba5ff06821 100644 --- a/cumulus/parachains/runtimes/constants/src/westend.rs +++ b/cumulus/parachains/runtimes/constants/src/westend.rs @@ -131,12 +131,40 @@ pub mod fee { /// Consensus-related. pub mod consensus { + use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; + /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included into the /// relay chain. - pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 1; + pub const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; /// How many parachain blocks are processed by the relay chain per parent. Limits the number of /// blocks authored per slot. pub const BLOCK_PROCESSING_VELOCITY: u32 = 1; /// Relay chain slot duration, in milliseconds. pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; + + /// We allow for 2 seconds of compute with a 6 second average block. + pub const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( + WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, + ); + + /// This determines the average expected block time that we are targeting. + /// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. + /// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked + /// up by `pallet_aura` to implement `fn slot_duration()`. + /// + /// Change this to adjust the block time. + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; +} + +/// Time-related +pub mod time { + use polkadot_core_primitives::BlockNumber; + + // Time is measured by number of blocks. + pub const MINUTES: BlockNumber = + 60_000 / (super::consensus::MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; } diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index b8da276567e03c06ad1ea627cf50fbcae6cf46a4..dcc6c4e853a39d6acb15e205256141de1db4ed2e 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -18,7 +18,7 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } # Substrate @@ -71,6 +71,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -87,6 +88,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs index 7b89f2df807734086031797915bde30b2eff77e2..681b95ce6a535ca1dd7696dac0b647aec4c8709b 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/contracts.rs @@ -72,5 +72,6 @@ impl Config for Runtime { type RuntimeHoldReason = RuntimeHoldReason; type Debug = (); type Environment = (); + type ApiVersion = (); type Xcm = pallet_xcm::Pallet; } diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index f3ef5e421fb0e50ea5314a47b0ede8802fa69942..541978098caaefaccc7724f56d8ff1b819fe42ec 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -29,7 +29,7 @@ mod contracts; mod weights; mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::AggregateMessageOrigin; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; @@ -50,7 +50,7 @@ use frame_support::{ dispatch::DispatchClass, genesis_builder_helper::{build_config, create_default_config}, parameter_types, - traits::{ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, ConstU8}, + traits::{ConstBool, ConstU16, ConstU32, ConstU64, ConstU8}, weights::{ConstantMultiplier, Weight}, PalletId, }; @@ -58,11 +58,10 @@ use frame_system::limits::{BlockLength, BlockWeights}; pub use parachains_common as common; use parachains_common::{ impls::DealWithFees, message_queue::*, AccountId, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, MAXIMUM_BLOCK_WEIGHT, MINUTES, NORMAL_DISPATCH_RATIO, - SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; pub use parachains_common::{AuraId, Balance}; -use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::CollatorSelectionUpdateOrigin; #[cfg(any(feature = "std", test))] @@ -104,6 +103,8 @@ pub type Migrations = ( pallet_contracts::Migration, // unreleased cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); type EventRecord = frame_system::EventRecord< @@ -132,7 +133,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("contracts-rococo"), impl_name: create_runtime_str!("contracts-rococo"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, @@ -192,6 +193,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = pallet_timestamp::weights::SubstrateWeight; } @@ -201,6 +205,10 @@ impl pallet_authorship::Config for Runtime { type EventHandler = (CollatorSelection,); } +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + impl pallet_balances::Config for Runtime { type MaxLocks = ConstU32<50>; /// The type for recording an account's balance. @@ -208,7 +216,7 @@ impl pallet_balances::Config for Runtime { /// The ubiquitous event type. type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ConstU128; + type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = pallet_balances::weights::SubstrateWeight; type MaxReserves = ConstU32<50>; @@ -274,15 +282,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type OutboundXcmpMessageSource = XcmpQueue; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + impl pallet_insecure_randomness_collective_flip::Config for Runtime {} impl parachain_info::Config for Runtime {} @@ -338,9 +348,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -421,6 +431,7 @@ mod benches { [pallet_sudo, Sudo] [pallet_timestamp, Timestamp] [pallet_collator_selection, CollatorSelection] + [cumulus_pallet_parachain_system, ParachainSystem] [pallet_contracts, Contracts] [pallet_xcm, PalletXcmExtrinsicsBenchmark::] ); @@ -429,7 +440,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -437,6 +448,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION @@ -697,9 +717,22 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + xcm_config::RelayLocation::get(), + ExistentialDeposit::get() + ).into()); + } + use xcm::latest::prelude::*; use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -708,7 +741,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between Contracts-System-Para and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs index 7919faf400f46f124aaadf12191a9dd3d17896ba..e8f3209eb67f627efccc9e8a798da03b6a2280c0 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/xcm_config.rs @@ -37,16 +37,15 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use testnet_parachains_constants::rococo::currency::CENTS; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, - NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, + DenyThenTry, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, + IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + XcmFeeToAccount, }; use xcm_executor::XcmExecutor; @@ -79,8 +78,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index 2c28f2296512e2ec1488a3da12a3a2920e1fb41c..0bc3b510ed50e9ee590fd82961a7d08fb3f581b0 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -15,9 +15,9 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = "0.4.1" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -56,7 +56,6 @@ sp-version = { path = "../../../../../substrate/primitives/version", default-fea # Polkadot pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } pallet-xcm-benchmarks = { path = "../../../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false, optional = true } -polkadot-core-primitives = { path = "../../../../../polkadot/core-primitives", default-features = false } polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } polkadot-runtime-common = { path = "../../../../../polkadot/runtime/common", default-features = false } rococo-runtime-constants = { path = "../../../../../polkadot/runtime/rococo/constants", default-features = false } @@ -70,6 +69,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -86,6 +86,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", @@ -113,7 +114,6 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", - "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "rococo-runtime-constants/std", diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs index 3e47b1bc4cba9cbe679d1a648c935baf6f20c9c7..742dd50f6fa1f421d6ce4abf221e05f6902cc2ae 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs @@ -29,10 +29,9 @@ use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600, use parachains_common::{AccountId, Balance, BlockNumber}; use xcm::latest::prelude::*; -// TODO: check AccountId import pub struct CreditToCollatorPot; impl OnUnbalanced> for CreditToCollatorPot { - fn on_nonzero_unbalanced(credit: Credit) { + fn on_nonzero_unbalanced(credit: Credit) { let staking_pot = CollatorSelection::account_id(); let _ = >::resolve(&staking_pot, credit); } @@ -81,7 +80,7 @@ pub struct CoretimeAllocator; impl CoretimeInterface for CoretimeAllocator { type AccountId = AccountId; type Balance = Balance; - type RealyChainBlockNumberProvider = RelaychainDataProvider; + type RelayChainBlockNumberProvider = RelaychainDataProvider; fn request_core_count(count: CoreIndex) { use crate::coretime::CoretimeProviderCalls::RequestCoreCount; diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 66fc4b28cd23b50b2fb38250e96749251ed420fb..fcd7576b1e17aafcf9f97d13cfe7c1a1e37f14b0 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -33,7 +33,7 @@ mod coretime; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -53,7 +53,7 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use sp_api::impl_runtime_apis; @@ -70,7 +70,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::latest::prelude::*; use xcm_config::{ @@ -106,7 +106,11 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4,); +pub type Migrations = ( + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -129,7 +133,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("coretime-rococo"), impl_name: create_runtime_str!("coretime-rococo"), authoring_version: 1, - spec_version: 1_006_001, + spec_version: 1_007_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, @@ -202,6 +206,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -262,15 +269,17 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + parameter_types! { pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; } @@ -359,9 +368,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -486,7 +495,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -494,6 +503,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION @@ -697,6 +715,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -705,7 +729,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between AH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs index 8815312f3042801306b830a7a48ad659c12100a8..9f79cea831aed66a0d073109233731751cdf99ed 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs @@ -196,7 +196,7 @@ impl XcmWeightInfo for CoretimeRococoXcmWeight { XcmGeneric::::clear_transact_status() } fn universal_origin(_: &Junction) -> Weight { - XcmGeneric::::universal_origin() + Weight::MAX } fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index a11d049a3fc5c6e66428063a9b6c0a3626f1f6ae..719e7543e8886a803f773126eafbc77f34749ddb 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -312,16 +312,6 @@ impl WeightInfo { // Minimum execution time: 2_082_000 picoseconds. Weight::from_parts(2_144_000, 0) } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - pub fn universal_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 4_275_000 picoseconds. - Weight::from_parts(4_381_000, 1489) - .saturating_add(T::DbWeight::get().reads(1)) - } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs index 13816f8ce711238433b62400f9e1004ab34b7f76..37bb8809dabac0ce52192dc64d0aa54ee136dc25 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/xcm_config.rs @@ -38,17 +38,15 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, IsConcrete, NonFungibleAdapter, - ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -79,8 +77,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index f2e1757dd3ee69a8afb0d0a5206b68add9430b6b..a7d52dfd7849ec1a20ef400bd45f995374697409 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -15,9 +15,9 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = "0.4.1" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -31,9 +31,9 @@ pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = pallet-authorship = { path = "../../../../../substrate/frame/authorship", default-features = false } pallet-balances = { path = "../../../../../substrate/frame/balances", default-features = false } pallet-message-queue = { path = "../../../../../substrate/frame/message-queue", default-features = false } +pallet-broker = { path = "../../../../../substrate/frame/broker", default-features = false } pallet-multisig = { path = "../../../../../substrate/frame/multisig", default-features = false } pallet-session = { path = "../../../../../substrate/frame/session", default-features = false } -pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false } pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false } pallet-transaction-payment = { path = "../../../../../substrate/frame/transaction-payment", default-features = false } pallet-transaction-payment-rpc-runtime-api = { path = "../../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false } @@ -55,7 +55,6 @@ sp-version = { path = "../../../../../substrate/primitives/version", default-fea # Polkadot pallet-xcm = { path = "../../../../../polkadot/xcm/pallet-xcm", default-features = false } pallet-xcm-benchmarks = { path = "../../../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false, optional = true } -polkadot-core-primitives = { path = "../../../../../polkadot/core-primitives", default-features = false } polkadot-parachain-primitives = { path = "../../../../../polkadot/parachain", default-features = false } polkadot-runtime-common = { path = "../../../../../polkadot/runtime/common", default-features = false } westend-runtime-constants = { path = "../../../../../polkadot/runtime/westend/constants", default-features = false } @@ -69,6 +68,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -85,6 +85,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", @@ -98,11 +99,11 @@ std = [ "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", + "pallet-broker/std", "pallet-collator-selection/std", "pallet-message-queue/std", "pallet-multisig/std", "pallet-session/std", - "pallet-sudo/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-transaction-payment/std", @@ -111,7 +112,6 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", - "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "scale-info/std", @@ -148,10 +148,10 @@ runtime-benchmarks = [ "frame-system-benchmarking/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-broker/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", - "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-xcm-benchmarks/runtime-benchmarks", @@ -176,11 +176,11 @@ try-runtime = [ "pallet-aura/try-runtime", "pallet-authorship/try-runtime", "pallet-balances/try-runtime", + "pallet-broker/try-runtime", "pallet-collator-selection/try-runtime", "pallet-message-queue/try-runtime", "pallet-multisig/try-runtime", "pallet-session/try-runtime", - "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", "pallet-utility/try-runtime", @@ -191,3 +191,5 @@ try-runtime = [ ] experimental = ["pallet-aura/experimental"] + +fast-runtime = [] diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs index 60f8a125129ff1344a1799246e931acdb1d139d5..28dacd20cf305ebdbc57eb2a30e3c98e4f8853d9 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/build.rs @@ -19,7 +19,15 @@ fn main() { .with_current_project() .export_heap_base() .import_memory() - .build() + .build(); + + substrate_wasm_builder::WasmBuilder::new() + .with_current_project() + .set_file_name("fast_runtime_binary.rs") + .enable_feature("fast-runtime") + .import_memory() + .export_heap_base() + .build(); } #[cfg(not(feature = "std"))] diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs new file mode 100644 index 0000000000000000000000000000000000000000..41cbc62fa2115ff3828e6910b750622a91ff0251 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs @@ -0,0 +1,249 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use crate::*; +use codec::{Decode, Encode}; +use cumulus_pallet_parachain_system::RelaychainDataProvider; +use cumulus_primitives_core::relay_chain; +use frame_support::{ + parameter_types, + traits::{ + fungible::{Balanced, Credit}, + OnUnbalanced, + }, +}; +use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600, RCBlockNumberOf}; +use parachains_common::{AccountId, Balance, BlockNumber}; +use xcm::latest::prelude::*; + +pub struct CreditToCollatorPot; +impl OnUnbalanced> for CreditToCollatorPot { + fn on_nonzero_unbalanced(credit: Credit) { + let staking_pot = CollatorSelection::account_id(); + let _ = >::resolve(&staking_pot, credit); + } +} + +/// A type containing the encoding of the coretime pallet in the Relay chain runtime. Used to +/// construct any remote calls. The codec index must correspond to the index of `Coretime` in the +/// `construct_runtime` of the Relay chain. +#[derive(Encode, Decode)] +enum RelayRuntimePallets { + #[codec(index = 66)] + Coretime(CoretimeProviderCalls), +} + +/// Call encoding for the calls needed from the relay coretime pallet. +#[derive(Encode, Decode)] +enum CoretimeProviderCalls { + #[codec(index = 1)] + RequestCoreCount(CoreIndex), + #[codec(index = 2)] + RequestRevenueInfoAt(relay_chain::BlockNumber), + #[codec(index = 3)] + CreditAccount(AccountId, Balance), + #[codec(index = 4)] + AssignCore( + CoreIndex, + relay_chain::BlockNumber, + Vec<(CoreAssignment, PartsOf57600)>, + Option, + ), +} + +parameter_types! { + pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); +} + +parameter_types! { + pub storage CoreCount: Option = None; + pub storage CoretimeRevenue: Option<(BlockNumber, Balance)> = None; +} + +/// Type that implements the `CoretimeInterface` for the allocation of Coretime. Meant to operate +/// from the parachain context. That is, the parachain provides a market (broker) for the sale of +/// coretime, but assumes a `CoretimeProvider` (i.e. a Relay Chain) to actually provide cores. +pub struct CoretimeAllocator; +impl CoretimeInterface for CoretimeAllocator { + type AccountId = AccountId; + type Balance = Balance; + type RelayChainBlockNumberProvider = RelaychainDataProvider; + + fn request_core_count(count: CoreIndex) { + use crate::coretime::CoretimeProviderCalls::RequestCoreCount; + let request_core_count_call = RelayRuntimePallets::Coretime(RequestCoreCount(count)); + + // Weight for `request_core_count` from westend benchmarks: + // `ref_time` = 7889000 + (3 * 25000000) + (1 * 100000000) = 182889000 + // `proof_size` = 1636 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(190_000_000, 1700); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: call_weight, + call: request_core_count_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Request to update schedulable cores sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Failed to send request to update schedulable cores: {:?}", + e + ), + } + } + + fn request_revenue_info_at(when: RCBlockNumberOf) { + use crate::coretime::CoretimeProviderCalls::RequestRevenueInfoAt; + let request_revenue_info_at_call = + RelayRuntimePallets::Coretime(RequestRevenueInfoAt(when)); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(1000000000, 200000), + call: request_revenue_info_at_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Request for revenue information sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Request for revenue information failed to send: {:?}", + e + ), + } + } + + fn credit_account(who: Self::AccountId, amount: Self::Balance) { + use crate::coretime::CoretimeProviderCalls::CreditAccount; + let credit_account_call = RelayRuntimePallets::Coretime(CreditAccount(who, amount)); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: Weight::from_parts(1000000000, 200000), + call: credit_account_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Instruction to credit account sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Instruction to credit account failed to send: {:?}", + e + ), + } + } + + fn assign_core( + core: CoreIndex, + begin: RCBlockNumberOf, + assignment: Vec<(CoreAssignment, PartsOf57600)>, + end_hint: Option>, + ) { + use crate::coretime::CoretimeProviderCalls::AssignCore; + let assign_core_call = + RelayRuntimePallets::Coretime(AssignCore(core, begin, assignment, end_hint)); + + // Weight for `assign_core` from westend benchmarks: + // `ref_time` = 10177115 + (1 * 25000000) + (2 * 100000000) + (57600 * 13932) = 937660315 + // `proof_size` = 3612 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(980_000_000, 3800); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Native, + require_weight_at_most: call_weight, + call: assign_core_call.encode().into(), + }, + ]); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => log::debug!( + target: "runtime::coretime", + "Core assignment sent successfully." + ), + Err(e) => log::error!( + target: "runtime::coretime", + "Core assignment failed to send: {:?}", + e + ), + } + } + + fn check_notify_revenue_info() -> Option<(RCBlockNumberOf, Self::Balance)> { + let revenue = CoretimeRevenue::get(); + CoretimeRevenue::set(&None); + revenue + } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_notify_revenue_info(when: RCBlockNumberOf, revenue: Self::Balance) { + CoretimeRevenue::set(&Some((when, revenue))); + } +} + +impl pallet_broker::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type OnRevenue = CreditToCollatorPot; + #[cfg(feature = "fast-runtime")] + type TimeslicePeriod = ConstU32<10>; + #[cfg(not(feature = "fast-runtime"))] + type TimeslicePeriod = ConstU32<80>; + // We don't actually need any leases at launch but set to 10 in case we want to sudo some in. + type MaxLeasedCores = ConstU32<10>; + type MaxReservedCores = ConstU32<10>; + type Coretime = CoretimeAllocator; + type ConvertBalance = sp_runtime::traits::Identity; + type WeightInfo = weights::pallet_broker::WeightInfo; + type PalletId = BrokerPalletId; + type AdminOrigin = EnsureRoot; + type PriceAdapter = pallet_broker::Linear; +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index e1db9158ce90907031a35814ca6dd380d2bc2c0c..80eb7863803b62ed09d67ab703df7507360e5333 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -21,10 +21,19 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +/// Provides the `WASM_BINARY` build with `fast-runtime` feature enabled. +/// +/// This is for example useful for local test chains. +#[cfg(feature = "std")] +pub mod fast_runtime_binary { + include!(concat!(env!("OUT_DIR"), "/fast_runtime_binary.rs")); +} + +mod coretime; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -44,7 +53,7 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature, - AVERAGE_ON_INITIALIZE_RATIO, HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; use sp_api::impl_runtime_apis; @@ -61,11 +70,11 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::latest::prelude::*; use xcm_config::{ - FellowshipLocation, GovernanceLocation, WndRelayLocation, XcmOriginToTransactDispatchOrigin, + FellowshipLocation, GovernanceLocation, TokenRelayLocation, XcmOriginToTransactDispatchOrigin, }; /// The address format for describing accounts. @@ -97,7 +106,11 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (); +pub type Migrations = ( + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -120,7 +133,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("coretime-westend"), impl_name: create_runtime_str!("coretime-westend"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, @@ -193,6 +206,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -253,16 +269,16 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; } -impl parachain_info::Config for Runtime {} +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; parameter_types! { pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; @@ -290,6 +306,8 @@ impl pallet_message_queue::Config for Runtime { type ServiceWeight = MessageQueueServiceWeight; } +impl parachain_info::Config for Runtime {} + impl cumulus_pallet_aura_ext::Config for Runtime {} parameter_types! { @@ -305,7 +323,7 @@ pub type RootOrFellows = EitherOfDiverse< parameter_types! { /// The asset ID for the asset that we use to pay for message delivery fees. - pub FeeAssetId: AssetId = AssetId(WndRelayLocation::get()); + pub FeeAssetId: AssetId = AssetId(TokenRelayLocation::get()); /// The base fee for the message delivery fees. pub const BaseDeliveryFee: u128 = CENTS.saturating_mul(3); } @@ -350,9 +368,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -408,12 +426,6 @@ impl pallet_utility::Config for Runtime { type WeightInfo = weights::pallet_utility::WeightInfo; } -impl pallet_sudo::Config for Runtime { - type RuntimeCall = RuntimeCall; - type RuntimeEvent = RuntimeEvent; - type WeightInfo = pallet_sudo::weights::SubstrateWeight; -} - // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -445,8 +457,8 @@ construct_runtime!( Utility: pallet_utility = 40, Multisig: pallet_multisig = 41, - // Sudo - Sudo: pallet_sudo = 100, + // The main stage. + Broker: pallet_broker = 50, } ); @@ -457,6 +469,7 @@ mod benches { [cumulus_pallet_parachain_system, ParachainSystem] [pallet_timestamp, Timestamp] [pallet_balances, Balances] + [pallet_broker, Broker] [pallet_collator_selection, CollatorSelection] [pallet_session, SessionBench::] [cumulus_pallet_xcmp_queue, XcmpQueue] @@ -473,7 +486,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -481,6 +494,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION @@ -680,10 +702,16 @@ impl_runtime_apis! { impl cumulus_pallet_session_benchmarking::Config for Runtime {} use xcm::latest::prelude::*; - use xcm_config::WndRelayLocation; + use xcm_config::TokenRelayLocation; use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -692,7 +720,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between AH and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), @@ -707,7 +735,7 @@ impl_runtime_apis! { parameter_types! { pub ExistentialDepositAsset: Option = Some(( - WndRelayLocation::get(), + TokenRelayLocation::get(), ExistentialDeposit::get() ).into()); } @@ -721,13 +749,13 @@ impl_runtime_apis! { >; type AccountIdConverter = xcm_config::LocationToAccountId; fn valid_destination() -> Result { - Ok(WndRelayLocation::get()) + Ok(TokenRelayLocation::get()) } fn worst_case_holding(_depositable_count: u32) -> Assets { // just concrete assets according to relay chain. let assets: Vec = vec![ Asset { - id: AssetId(WndRelayLocation::get()), + id: AssetId(TokenRelayLocation::get()), fun: Fungible(1_000_000 * UNITS), } ]; @@ -737,8 +765,8 @@ impl_runtime_apis! { parameter_types! { pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( - WndRelayLocation::get(), - Asset { fun: Fungible(UNITS), id: AssetId(WndRelayLocation::get()) }, + TokenRelayLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(TokenRelayLocation::get()) }, )); pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; pub const TrustedReserve: Option<(Location, Asset)> = None; @@ -753,7 +781,7 @@ impl_runtime_apis! { fn get_asset() -> Asset { Asset { - id: AssetId(WndRelayLocation::get()), + id: AssetId(TokenRelayLocation::get()), fun: Fungible(UNITS), } } @@ -776,23 +804,23 @@ impl_runtime_apis! { } fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { - Ok((WndRelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + Ok((TokenRelayLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) } fn subscribe_origin() -> Result { - Ok(WndRelayLocation::get()) + Ok(TokenRelayLocation::get()) } fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { - let origin = WndRelayLocation::get(); - let assets: Assets = (AssetId(WndRelayLocation::get()), 1_000 * UNITS).into(); + let origin = TokenRelayLocation::get(); + let assets: Assets = (AssetId(TokenRelayLocation::get()), 1_000 * UNITS).into(); let ticket = Location { parents: 0, interior: Here }; Ok((origin, ticket, assets)) } fn fee_asset() -> Result { Ok(Asset { - id: AssetId(WndRelayLocation::get()), + id: AssetId(TokenRelayLocation::get()), fun: Fungible(1_000_000 * UNITS), }) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs index 0303151d7f83dfc5957e7346b1c4ef2950b6dc01..1c9119361985cd541ca42dce6eb8d2f355a29c6c 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -14,6 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . +//! Autogenerated weights for `cumulus_pallet_parachain_system` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=cumulus_pallet_parachain_system +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -22,32 +47,31 @@ use frame_support::{traits::Get, weights::Weight}; use core::marker::PhantomData; -/// Weight functions for `cumulus_pallet_xcmp_queue`. +/// Weight functions for `cumulus_pallet_parachain_system`. pub struct WeightInfo(PhantomData); impl cumulus_pallet_parachain_system::WeightInfo for WeightInfo { - /// Storage: ParachainSystem LastDmqMqcHead (r:1 w:1) - /// Proof Skipped: ParachainSystem LastDmqMqcHead (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem ReservedDmpWeightOverride (r:1 w:0) - /// Proof Skipped: ParachainSystem ReservedDmpWeightOverride (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue ServiceHead (r:1 w:1) - /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) - /// Storage: ParachainSystem ProcessedDownwardMessages (r:0 w:1) - /// Proof Skipped: ParachainSystem ProcessedDownwardMessages (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: MessageQueue Pages (r:0 w:16) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `ParachainSystem::LastDmqMqcHead` (r:1 w:1) + /// Proof: `ParachainSystem::LastDmqMqcHead` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ProcessedDownwardMessages` (r:0 w:1) + /// Proof: `ParachainSystem::ProcessedDownwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `MessageQueue::Pages` (r:0 w:1000) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. fn enqueue_inbound_downward_messages(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `12` - // Estimated: `8013` - // Minimum execution time: 1_645_000 picoseconds. - Weight::from_parts(1_717_000, 0) - .saturating_add(Weight::from_parts(0, 8013)) - // Standard Error: 12_258 - .saturating_add(Weight::from_parts(24_890_934, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `48` + // Estimated: `3517` + // Minimum execution time: 2_080_000 picoseconds. + Weight::from_parts(2_157_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) + // Standard Error: 33_906 + .saturating_add(Weight::from_parts(196_603_239, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } -} \ No newline at end of file +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs index 124571118aa129e1489aaaf1ebeabbde41ed13c4..0b0524339aa761053a7c559a3a2ab725c0cb6c22 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `cumulus_pallet_xcmp_queue` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=cumulus_pallet_xcmp_queue +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_xcmp_queue.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,14 +50,14 @@ use core::marker::PhantomData; /// Weight functions for `cumulus_pallet_xcmp_queue`. pub struct WeightInfo(PhantomData); impl cumulus_pallet_xcmp_queue::WeightInfo for WeightInfo { - /// Storage: XcmpQueue QueueConfig (r:1 w:1) - /// Proof Skipped: XcmpQueue QueueConfig (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `XcmpQueue::QueueConfig` (r:1 w:1) + /// Proof: `XcmpQueue::QueueConfig` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_config_with_u32() -> Weight { // Proof Size summary in bytes: // Measured: `76` // Estimated: `1561` - // Minimum execution time: 5_621_000 picoseconds. - Weight::from_parts(5_845_000, 0) + // Minimum execution time: 3_796_000 picoseconds. + Weight::from_parts(4_027_000, 0) .saturating_add(Weight::from_parts(0, 1561)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -74,8 +76,8 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `82` // Estimated: `3517` - // Minimum execution time: 14_000_000 picoseconds. - Weight::from_parts(15_000_000, 0) + // Minimum execution time: 9_990_000 picoseconds. + Weight::from_parts(10_439_000, 0) .saturating_add(Weight::from_parts(0, 3517)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) @@ -86,8 +88,8 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `76` // Estimated: `1561` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(3_000_000, 0) + // Minimum execution time: 2_394_000 picoseconds. + Weight::from_parts(2_493_000, 0) .saturating_add(Weight::from_parts(0, 1561)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -98,8 +100,8 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `111` // Estimated: `1596` - // Minimum execution time: 4_000_000 picoseconds. - Weight::from_parts(4_000_000, 0) + // Minimum execution time: 3_283_000 picoseconds. + Weight::from_parts(3_388_000, 0) .saturating_add(Weight::from_parts(0, 1596)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -108,14 +110,14 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 44_000_000 picoseconds. - Weight::from_parts(45_000_000, 0) + // Minimum execution time: 5_974_000 picoseconds. + Weight::from_parts(6_166_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) - /// Storage: `XcmpQueue::InboundXcmpMessages` (r:1 w:1) - /// Proof: `XcmpQueue::InboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6bedc49980ba3aa32b0a189290fd036649` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6bedc49980ba3aa32b0a189290fd036649` (r:1 w:1) /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) @@ -130,20 +132,22 @@ impl cumulus_pallet_xcmp_queue::WeightInfo for WeightIn // Proof Size summary in bytes: // Measured: `65711` // Estimated: `69176` - // Minimum execution time: 67_000_000 picoseconds. - Weight::from_parts(73_000_000, 0) + // Minimum execution time: 117_856_000 picoseconds. + Weight::from_parts(119_808_000, 0) .saturating_add(Weight::from_parts(0, 69176)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6b345d8e88afa015075c945637c07e8f20` (r:1 w:1) - fn on_idle_large_msg() -> Weight { + /// Storage: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6bedc49980ba3aa32b0a189290fd036649` (r:1 w:1) + /// Proof: UNKNOWN KEY `0x7b3237373ffdfeb1cab4222e3b520d6bedc49980ba3aa32b0a189290fd036649` (r:1 w:1) + fn on_idle_large_msg() -> Weight { // Proof Size summary in bytes: // Measured: `65710` // Estimated: `69175` - // Minimum execution time: 49_000_000 picoseconds. - Weight::from_parts(55_000_000, 0) + // Minimum execution time: 52_555_000 picoseconds. + Weight::from_parts(54_052_000, 0) .saturating_add(Weight::from_parts(0, 69175)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs index 46f8113939e4d4fa3f26ff03d665eec6b4120a6b..b4b7cbf05a5ec757bc3a60e08ef62678029b47f2 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `frame_system` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=frame_system +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -53,80 +55,99 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_432_000 picoseconds. - Weight::from_parts(2_458_000, 0) + // Minimum execution time: 1_584_000 picoseconds. + Weight::from_parts(2_117_975, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 0 - .saturating_add(Weight::from_parts(367, 0).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(384, 0).saturating_mul(b.into())) } /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_911_000 picoseconds. - Weight::from_parts(8_031_000, 0) + // Minimum execution time: 4_607_000 picoseconds. + Weight::from_parts(14_948_582, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1_405, 0).saturating_mul(b.into())) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_673, 0).saturating_mul(b.into())) } - /// Storage: System Digest (r:1 w:1) - /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: unknown `0x3a686561707061676573` (r:0 w:1) - /// Proof Skipped: unknown `0x3a686561707061676573` (r:0 w:1) + /// Storage: `System::Digest` (r:1 w:1) + /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: UNKNOWN KEY `0x3a686561707061676573` (r:0 w:1) + /// Proof: UNKNOWN KEY `0x3a686561707061676573` (r:0 w:1) fn set_heap_pages() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 4_304_000 picoseconds. - Weight::from_parts(4_553_000, 0) + // Minimum execution time: 2_681_000 picoseconds. + Weight::from_parts(2_877_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpgradeRestrictionSignal` (r:1 w:0) + /// Proof: `ParachainSystem::UpgradeRestrictionSignal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingValidationCode` (r:1 w:1) + /// Proof: `ParachainSystem::PendingValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::NewValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::NewValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::DidSetValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::DidSetValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn set_code() -> Weight { - Weight::from_parts(1_000_000, 0) + // Proof Size summary in bytes: + // Measured: `164` + // Estimated: `1649` + // Minimum execution time: 95_893_701_000 picoseconds. + Weight::from_parts(98_086_094_000, 0) + .saturating_add(Weight::from_parts(0, 1649)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: Skipped Metadata (r:0 w:0) - /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `i` is `[0, 1000]`. fn set_storage(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_493_000 picoseconds. - Weight::from_parts(2_523_000, 0) + // Minimum execution time: 1_597_000 picoseconds. + Weight::from_parts(1_660_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1_594 - .saturating_add(Weight::from_parts(663_439, 0).saturating_mul(i.into())) + // Standard Error: 1_871 + .saturating_add(Weight::from_parts(748_346, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } - /// Storage: Skipped Metadata (r:0 w:0) - /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `i` is `[0, 1000]`. fn kill_storage(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_492_000 picoseconds. - Weight::from_parts(2_526_000, 0) + // Minimum execution time: 1_625_000 picoseconds. + Weight::from_parts(1_669_000, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 784 - .saturating_add(Weight::from_parts(493_844, 0).saturating_mul(i.into())) + // Standard Error: 903 + .saturating_add(Weight::from_parts(561_709, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } - /// Storage: Skipped Metadata (r:0 w:0) - /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// Storage: `Skipped::Metadata` (r:0 w:0) + /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `68 + p * (69 ±0)` - // Estimated: `66 + p * (70 ±0)` - // Minimum execution time: 4_200_000 picoseconds. - Weight::from_parts(4_288_000, 0) - .saturating_add(Weight::from_parts(0, 66)) - // Standard Error: 1_195 - .saturating_add(Weight::from_parts(1_021_563, 0).saturating_mul(p.into())) + // Measured: `71 + p * (69 ±0)` + // Estimated: `72 + p * (70 ±0)` + // Minimum execution time: 3_306_000 picoseconds. + Weight::from_parts(3_412_000, 0) + .saturating_add(Weight::from_parts(0, 72)) + // Standard Error: 1_366 + .saturating_add(Weight::from_parts(1_138_953, 0).saturating_mul(p.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(p.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) @@ -137,25 +158,33 @@ impl frame_system::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 33_027_000 picoseconds. - Weight::from_parts(33_027_000, 0) + // Minimum execution time: 7_834_000 picoseconds. + Weight::from_parts(8_344_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `System::AuthorizedUpgrade` (r:1 w:1) /// Proof: `System::AuthorizedUpgrade` (`max_values`: Some(1), `max_size`: Some(33), added: 528, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:1) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) - /// Proof: UNKNOWN KEY `0x3a636f6465` (r:0 w:1) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::UpgradeRestrictionSignal` (r:1 w:0) + /// Proof: `ParachainSystem::UpgradeRestrictionSignal` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingValidationCode` (r:1 w:1) + /// Proof: `ParachainSystem::PendingValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::NewValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::NewValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::DidSetValidationCode` (r:0 w:1) + /// Proof: `ParachainSystem::DidSetValidationCode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn apply_authorized_upgrade() -> Weight { // Proof Size summary in bytes: - // Measured: `22` - // Estimated: `1518` - // Minimum execution time: 118_101_992_000 picoseconds. - Weight::from_parts(118_101_992_000, 0) - .saturating_add(Weight::from_parts(0, 1518)) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `186` + // Estimated: `1671` + // Minimum execution time: 98_682_277_000 picoseconds. + Weight::from_parts(101_609_257_000, 0) + .saturating_add(Weight::from_parts(0, 1671)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs index 28e708f0e2a33da7419fe821ab1fb72806c273b1..f1050b3ae636261ff21674c3bb34c05bf6d232c5 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; pub mod pallet_balances; +pub mod pallet_broker; pub mod pallet_collator_selection; pub mod pallet_message_queue; pub mod pallet_multisig; diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs index 2fc1495c804fcc2a692925af8f53ac57b84863cd..c4770a7c94381cfca9404a6577c703164f215918 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_balances.rs @@ -17,23 +17,25 @@ //! Autogenerated weights for `pallet_balances` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-01-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-8idpd4bs-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// target/production/polkadot-parachain +// ./target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 -// --extrinsic=* +// --chain=coretime-westend-dev // --wasm-execution=compiled -// --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_balances -// --chain=coretime-westend-dev +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json // --header=./cumulus/file_header.txt // --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ @@ -54,8 +56,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 41_147_000 picoseconds. - Weight::from_parts(41_829_000, 0) + // Minimum execution time: 42_773_000 picoseconds. + Weight::from_parts(43_292_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -66,8 +68,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 32_566_000 picoseconds. - Weight::from_parts(33_012_000, 0) + // Minimum execution time: 34_023_000 picoseconds. + Weight::from_parts(34_513_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -78,8 +80,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 11_435_000 picoseconds. - Weight::from_parts(11_717_000, 0) + // Minimum execution time: 11_685_000 picoseconds. + Weight::from_parts(12_103_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -90,8 +92,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 15_941_000 picoseconds. - Weight::from_parts(16_341_000, 0) + // Minimum execution time: 16_233_000 picoseconds. + Weight::from_parts(16_706_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -102,8 +104,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 42_592_000 picoseconds. - Weight::from_parts(43_111_000, 0) + // Minimum execution time: 43_909_000 picoseconds. + Weight::from_parts(44_683_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -114,8 +116,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 40_925_000 picoseconds. - Weight::from_parts(41_743_000, 0) + // Minimum execution time: 42_081_000 picoseconds. + Weight::from_parts(42_553_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -126,8 +128,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 14_117_000 picoseconds. - Weight::from_parts(14_418_000, 0) + // Minimum execution time: 14_413_000 picoseconds. + Weight::from_parts(14_827_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -139,11 +141,11 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 13_855_000 picoseconds. - Weight::from_parts(14_108_000, 0) + // Minimum execution time: 14_189_000 picoseconds. + Weight::from_parts(14_587_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 11_673 - .saturating_add(Weight::from_parts(12_675_264, 0).saturating_mul(u.into())) + // Standard Error: 10_909 + .saturating_add(Weight::from_parts(13_040_864, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) @@ -154,8 +156,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1501` - // Minimum execution time: 4_820_000 picoseconds. - Weight::from_parts(5_152_000, 0) + // Minimum execution time: 5_218_000 picoseconds. + Weight::from_parts(5_562_000, 0) .saturating_add(Weight::from_parts(0, 1501)) .saturating_add(T::DbWeight::get().reads(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs new file mode 100644 index 0000000000000000000000000000000000000000..8727b9633b1f0eaca2bcaa5f7a43d832f6abbe9b --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs @@ -0,0 +1,516 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_broker` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=pallet_broker +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_broker`. +pub struct WeightInfo(PhantomData); +impl pallet_broker::WeightInfo for WeightInfo { + /// Storage: `Broker::Configuration` (r:0 w:1) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + fn configure() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_944_000 picoseconds. + Weight::from_parts(2_045_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + fn reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `10888` + // Estimated: `13506` + // Minimum execution time: 21_158_000 picoseconds. + Weight::from_parts(21_572_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + fn unreserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `12090` + // Estimated: `13506` + // Minimum execution time: 20_497_000 picoseconds. + Weight::from_parts(20_995_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::LastRelayChainBlockNumber` (r:1 w:0) + /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_lease() -> Weight { + // Proof Size summary in bytes: + // Measured: `146` + // Estimated: `1631` + // Minimum execution time: 10_280_000 picoseconds. + Weight::from_parts(10_686_000, 0) + .saturating_add(Weight::from_parts(0, 1631)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::LastRelayChainBlockNumber` (r:1 w:0) + /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:0 w:1) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:20) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn start_sales(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `12247` + // Estimated: `13732` + // Minimum execution time: 61_020_000 picoseconds. + Weight::from_parts(63_240_622, 0) + .saturating_add(Weight::from_parts(0, 13732)) + // Standard Error: 102 + .saturating_add(Weight::from_parts(255, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(26)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:1 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:0 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn purchase() -> Weight { + // Proof Size summary in bytes: + // Measured: `316` + // Estimated: `3593` + // Minimum execution time: 30_627_000 picoseconds. + Weight::from_parts(31_648_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:1 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::AllowedRenewals` (r:1 w:2) + /// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn renew() -> Weight { + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `4698` + // Minimum execution time: 57_701_000 picoseconds. + Weight::from_parts(59_825_000, 0) + .saturating_add(Weight::from_parts(0, 4698)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 12_898_000 picoseconds. + Weight::from_parts(13_506_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Regions` (r:1 w:2) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn partition() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 14_284_000 picoseconds. + Weight::from_parts(14_791_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Regions` (r:1 w:3) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn interlace() -> Weight { + // Proof Size summary in bytes: + // Measured: `357` + // Estimated: `3550` + // Minimum execution time: 15_570_000 picoseconds. + Weight::from_parts(16_158_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn assign() -> Weight { + // Proof Size summary in bytes: + // Measured: `735` + // Estimated: `4681` + // Minimum execution time: 23_329_000 picoseconds. + Weight::from_parts(24_196_000, 0) + .saturating_add(Weight::from_parts(0, 4681)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolIo` (r:2 w:2) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolContribution` (r:0 w:1) + /// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `801` + // Estimated: `5996` + // Minimum execution time: 29_288_000 picoseconds. + Weight::from_parts(30_066_000, 0) + .saturating_add(Weight::from_parts(0, 5996)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `Broker::InstaPoolContribution` (r:1 w:1) + /// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolHistory` (r:3 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `m` is `[1, 3]`. + fn claim_revenue(m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `652` + // Estimated: `6196 + m * (2520 ±0)` + // Minimum execution time: 54_833_000 picoseconds. + Weight::from_parts(55_577_423, 0) + .saturating_add(Weight::from_parts(0, 6196)) + // Standard Error: 35_105 + .saturating_add(Weight::from_parts(1_267_911, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) + .saturating_add(T::DbWeight::get().writes(5)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(m.into())) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn purchase_credit() -> Weight { + // Proof Size summary in bytes: + // Measured: `215` + // Estimated: `3680` + // Minimum execution time: 55_289_000 picoseconds. + Weight::from_parts(56_552_000, 0) + .saturating_add(Weight::from_parts(0, 3680)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Regions` (r:1 w:1) + /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + fn drop_region() -> Weight { + // Proof Size summary in bytes: + // Measured: `465` + // Estimated: `3550` + // Minimum execution time: 39_736_000 picoseconds. + Weight::from_parts(41_346_000, 0) + .saturating_add(Weight::from_parts(0, 3550)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolContribution` (r:1 w:1) + /// Proof: `Broker::InstaPoolContribution` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + fn drop_contribution() -> Weight { + // Proof Size summary in bytes: + // Measured: `463` + // Estimated: `3533` + // Minimum execution time: 57_319_000 picoseconds. + Weight::from_parts(60_204_000, 0) + .saturating_add(Weight::from_parts(0, 3533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn drop_history() -> Weight { + // Proof Size summary in bytes: + // Measured: `857` + // Estimated: `3593` + // Minimum execution time: 85_216_000 picoseconds. + Weight::from_parts(91_144_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::AllowedRenewals` (r:1 w:1) + /// Proof: `Broker::AllowedRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + fn drop_renewal() -> Weight { + // Proof Size summary in bytes: + // Measured: `556` + // Estimated: `4698` + // Minimum execution time: 32_331_000 picoseconds. + Weight::from_parts(39_877_000, 0) + .saturating_add(Weight::from_parts(0, 4698)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// The range of component `n` is `[0, 1000]`. + fn request_core_count(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 18_128_000 picoseconds. + Weight::from_parts(19_061_234, 0) + .saturating_add(Weight::from_parts(0, 3539)) + // Standard Error: 48 + .saturating_add(Weight::from_parts(141, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::CoreCountInbox` (r:1 w:1) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn process_core_count(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `266` + // Estimated: `1487` + // Minimum execution time: 5_368_000 picoseconds. + Weight::from_parts(5_837_005, 0) + .saturating_add(Weight::from_parts(0, 1487)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn process_revenue() -> Weight { + // Proof Size summary in bytes: + // Measured: `447` + // Estimated: `6196` + // Minimum execution time: 36_047_000 picoseconds. + Weight::from_parts(37_101_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:20) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// The range of component `n` is `[0, 1000]`. + fn rotate_sale(_n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `12194` + // Estimated: `13506` + // Minimum execution time: 48_158_000 picoseconds. + Weight::from_parts(49_891_920, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(25)) + } + /// Storage: `Broker::InstaPoolIo` (r:1 w:0) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::InstaPoolHistory` (r:0 w:1) + /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + fn process_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3493` + // Minimum execution time: 5_911_000 picoseconds. + Weight::from_parts(6_173_000, 0) + .saturating_add(Weight::from_parts(0, 3493)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Workplan` (r:1 w:1) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workload` (r:1 w:1) + /// Proof: `Broker::Workload` (`max_values`: None, `max_size`: Some(1212), added: 3687, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn process_core_schedule() -> Weight { + // Proof Size summary in bytes: + // Measured: `1321` + // Estimated: `4786` + // Minimum execution time: 30_140_000 picoseconds. + Weight::from_parts(30_912_000, 0) + .saturating_add(Weight::from_parts(0, 4786)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn request_revenue_info_at() -> Weight { + // Proof Size summary in bytes: + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 13_684_000 picoseconds. + Weight::from_parts(14_252_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Broker::CoreCountInbox` (r:0 w:1) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + fn notify_core_count() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_718_000 picoseconds. + Weight::from_parts(1_843_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Broker::Status` (r:1 w:1) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::CoreCountInbox` (r:1 w:0) + /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) + /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn do_tick_base() -> Weight { + // Proof Size summary in bytes: + // Measured: `398` + // Estimated: `3863` + // Minimum execution time: 11_771_000 picoseconds. + Weight::from_parts(12_120_000, 0) + .saturating_add(Weight::from_parts(0, 3863)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs index 2adddecab264945884d8b4620b9dff9868bfc4f0..2341890c314ec422aebf30b27111502a01909c25 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_collator_selection` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_collator_selection +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_collator_selection.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,196 +50,236 @@ use core::marker::PhantomData; /// Weight functions for `pallet_collator_selection`. pub struct WeightInfo(PhantomData); impl pallet_collator_selection::WeightInfo for WeightInfo { - /// Storage: Session NextKeys (r:100 w:0) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: CollatorSelection Invulnerables (r:0 w:1) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) - /// The range of component `b` is `[1, 100]`. + /// Storage: `Session::NextKeys` (r:20 w:0) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CollatorSelection::Invulnerables` (r:0 w:1) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// The range of component `b` is `[1, 20]`. fn set_invulnerables(b: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `214 + b * (78 ±0)` - // Estimated: `1203 + b * (2554 ±0)` - // Minimum execution time: 14_426_000 picoseconds. - Weight::from_parts(14_971_974, 0) - .saturating_add(Weight::from_parts(0, 1203)) - // Standard Error: 2_914 - .saturating_add(Weight::from_parts(2_604_699, 0).saturating_mul(b.into())) + // Measured: `164 + b * (79 ±0)` + // Estimated: `1155 + b * (2555 ±0)` + // Minimum execution time: 11_038_000 picoseconds. + Weight::from_parts(8_347_616, 0) + .saturating_add(Weight::from_parts(0, 1155)) + // Standard Error: 5_166 + .saturating_add(Weight::from_parts(3_025_311, 0).saturating_mul(b.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(b.into()))) .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 2554).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 2555).saturating_mul(b.into())) } - /// Storage: CollatorSelection DesiredCandidates (r:0 w:1) - /// Proof: CollatorSelection DesiredCandidates (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn set_desired_candidates() -> Weight { + /// Storage: `Session::NextKeys` (r:1 w:0) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:1) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `b` is `[1, 19]`. + /// The range of component `c` is `[1, 99]`. + fn add_invulnerable(b: u32, c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 6_977_000 picoseconds. - Weight::from_parts(7_246_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `720 + b * (32 ±0) + c * (53 ±0)` + // Estimated: `6287 + b * (37 ±0) + c * (53 ±0)` + // Minimum execution time: 36_983_000 picoseconds. + Weight::from_parts(37_900_558, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 6_860 + .saturating_add(Weight::from_parts(94_160, 0).saturating_mul(b.into())) + // Standard Error: 1_300 + .saturating_add(Weight::from_parts(119_010, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(Weight::from_parts(0, 37).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 53).saturating_mul(c.into())) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:0) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:1) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// The range of component `b` is `[5, 20]`. + fn remove_invulnerable(b: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `82 + b * (32 ±0)` + // Estimated: `6287` + // Minimum execution time: 10_432_000 picoseconds. + Weight::from_parts(10_460_489, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_803 + .saturating_add(Weight::from_parts(143_162, 0).saturating_mul(b.into())) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `CollatorSelection::CandidacyBond` (r:0 w:1) - /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - fn set_candidacy_bond(_c: u32, _k: u32) -> Weight { + /// Storage: `CollatorSelection::DesiredCandidates` (r:0 w:1) + /// Proof: `CollatorSelection::DesiredCandidates` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn set_desired_candidates() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_937_000 picoseconds. - Weight::from_parts(8_161_000, 0) + // Minimum execution time: 4_302_000 picoseconds. + Weight::from_parts(4_508_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: CollatorSelection Candidates (r:1 w:1) - /// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(48002), added: 48497, mode: MaxEncodedLen) - /// Storage: CollatorSelection DesiredCandidates (r:1 w:0) - /// Proof: CollatorSelection DesiredCandidates (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: CollatorSelection Invulnerables (r:1 w:0) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) - /// Storage: Session NextKeys (r:1 w:0) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: CollatorSelection CandidacyBond (r:1 w:0) - /// Proof: CollatorSelection CandidacyBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) - /// Storage: CollatorSelection LastAuthoredBlock (r:0 w:1) - /// Proof: CollatorSelection LastAuthoredBlock (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) - /// The range of component `c` is `[1, 999]`. - fn register_as_candidate(c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1104 + c * (48 ±0)` - // Estimated: `49487 + c * (49 ±0)` - // Minimum execution time: 42_275_000 picoseconds. - Weight::from_parts(33_742_215, 0) - .saturating_add(Weight::from_parts(0, 49487)) - // Standard Error: 1_291 - .saturating_add(Weight::from_parts(103_381, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) - .saturating_add(Weight::from_parts(0, 49).saturating_mul(c.into())) - } - /// Storage: CollatorSelection Candidates (r:1 w:1) - /// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(48002), added: 48497, mode: MaxEncodedLen) - /// Storage: CollatorSelection LastAuthoredBlock (r:0 w:1) - /// Proof: CollatorSelection LastAuthoredBlock (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) - /// The range of component `c` is `[6, 1000]`. - fn leave_intent(c: u32, ) -> Weight { + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:1) + /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:100 w:100) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:100) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 100]`. + /// The range of component `k` is `[0, 100]`. + fn set_candidacy_bond(c: u32, k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `428 + c * (48 ±0)` - // Estimated: `49487` - // Minimum execution time: 33_404_000 picoseconds. - Weight::from_parts(22_612_617, 0) - .saturating_add(Weight::from_parts(0, 49487)) - // Standard Error: 1_341 - .saturating_add(Weight::from_parts(105_669, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `0 + c * (180 ±0) + k * (112 ±0)` + // Estimated: `6287 + c * (901 ±29) + k * (901 ±29)` + // Minimum execution time: 7_712_000 picoseconds. + Weight::from_parts(7_935_000, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 153_204 + .saturating_add(Weight::from_parts(5_173_626, 0).saturating_mul(c.into())) + // Standard Error: 153_204 + .saturating_add(Weight::from_parts(4_883_030, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 901).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(0, 901).saturating_mul(k.into())) } + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:0) + /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// The range of component `c` is `[4, 100]`. fn update_bond(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `306 + c * (50 ±0)` + // Measured: `250 + c * (50 ±0)` // Estimated: `6287` - // Minimum execution time: 34_814_000 picoseconds. - Weight::from_parts(36_371_520, 0) + // Minimum execution time: 22_767_000 picoseconds. + Weight::from_parts(25_594_856, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_391 - .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + // Standard Error: 1_814 + .saturating_add(Weight::from_parts(110_451, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `Session::NextKeys` (r:1 w:0) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:0) + /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:1) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// The range of component `c` is `[1, 99]`. + fn register_as_candidate(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `687 + c * (52 ±0)` + // Estimated: `6287 + c * (54 ±0)` + // Minimum execution time: 30_792_000 picoseconds. + Weight::from_parts(34_485_582, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_421 + .saturating_add(Weight::from_parts(152_013, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(Weight::from_parts(0, 54).saturating_mul(c.into())) } + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::CandidacyBond` (r:1 w:0) + /// Proof: `CollatorSelection::CandidacyBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Session::NextKeys` (r:1 w:0) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:2) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// The range of component `c` is `[4, 100]`. fn take_candidate_slot(c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `306 + c * (50 ±0)` + // Measured: `855 + c * (52 ±0)` + // Estimated: `6287 + c * (55 ±0)` + // Minimum execution time: 45_538_000 picoseconds. + Weight::from_parts(50_758_223, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 2_779 + .saturating_add(Weight::from_parts(149_419, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(Weight::from_parts(0, 55).saturating_mul(c.into())) + } + /// Storage: `CollatorSelection::CandidateList` (r:1 w:1) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:1) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// The range of component `c` is `[4, 100]`. + fn leave_intent(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `277 + c * (48 ±0)` // Estimated: `6287` - // Minimum execution time: 34_814_000 picoseconds. - Weight::from_parts(36_371_520, 0) + // Minimum execution time: 26_356_000 picoseconds. + Weight::from_parts(29_910_328, 0) .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 2_391 - .saturating_add(Weight::from_parts(201_700, 0).saturating_mul(c.into())) + // Standard Error: 2_159 + .saturating_add(Weight::from_parts(123_421, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: System Account (r:2 w:2) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// Storage: System BlockWeight (r:1 w:1) - /// Proof: System BlockWeight (max_values: Some(1), max_size: Some(48), added: 543, mode: MaxEncodedLen) - /// Storage: CollatorSelection LastAuthoredBlock (r:0 w:1) - /// Proof: CollatorSelection LastAuthoredBlock (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:0 w:1) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn note_author() -> Weight { // Proof Size summary in bytes: - // Measured: `155` + // Measured: `103` // Estimated: `6196` - // Minimum execution time: 44_415_000 picoseconds. - Weight::from_parts(44_732_000, 0) + // Minimum execution time: 36_377_000 picoseconds. + Weight::from_parts(37_121_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: Session NextKeys (r:1 w:0) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: CollatorSelection Invulnerables (r:1 w:1) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(641), added: 1136, mode: MaxEncodedLen) - /// Storage: CollatorSelection Candidates (r:1 w:1) - /// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(4802), added: 5297, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// The range of component `b` is `[1, 19]`. - /// The range of component `c` is `[1, 99]`. - fn add_invulnerable(b: u32, c: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `757 + b * (32 ±0) + c * (53 ±0)` - // Estimated: `6287 + b * (37 ±0) + c * (53 ±0)` - // Minimum execution time: 52_720_000 picoseconds. - Weight::from_parts(56_102_459, 0) - .saturating_add(Weight::from_parts(0, 6287)) - // Standard Error: 12_957 - .saturating_add(Weight::from_parts(26_422, 0).saturating_mul(b.into())) - // Standard Error: 2_456 - .saturating_add(Weight::from_parts(128_528, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(Weight::from_parts(0, 37).saturating_mul(b.into())) - .saturating_add(Weight::from_parts(0, 53).saturating_mul(c.into())) - } - /// Storage: CollatorSelection Invulnerables (r:1 w:1) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) - /// The range of component `b` is `[1, 100]`. - fn remove_invulnerable(b: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `119 + b * (32 ±0)` - // Estimated: `4687` - // Minimum execution time: 183_054_000 picoseconds. - Weight::from_parts(197_205_427, 0) - .saturating_add(Weight::from_parts(0, 4687)) - // Standard Error: 13_533 - .saturating_add(Weight::from_parts(376_231, 0).saturating_mul(b.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - /// Storage: CollatorSelection Candidates (r:1 w:0) - /// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(48002), added: 48497, mode: MaxEncodedLen) - /// Storage: CollatorSelection LastAuthoredBlock (r:999 w:0) - /// Proof: CollatorSelection LastAuthoredBlock (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) - /// Storage: CollatorSelection Invulnerables (r:1 w:0) - /// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) - /// Storage: System BlockWeight (r:1 w:1) - /// Proof: System BlockWeight (max_values: Some(1), max_size: Some(48), added: 543, mode: MaxEncodedLen) - /// Storage: System Account (r:995 w:995) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - /// The range of component `r` is `[1, 1000]`. - /// The range of component `c` is `[1, 1000]`. + /// Storage: `CollatorSelection::CandidateList` (r:1 w:0) + /// Proof: `CollatorSelection::CandidateList` (`max_values`: Some(1), `max_size`: Some(4802), added: 5297, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::LastAuthoredBlock` (r:100 w:0) + /// Proof: `CollatorSelection::LastAuthoredBlock` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::Invulnerables` (r:1 w:0) + /// Proof: `CollatorSelection::Invulnerables` (`max_values`: Some(1), `max_size`: Some(641), added: 1136, mode: `MaxEncodedLen`) + /// Storage: `CollatorSelection::DesiredCandidates` (r:1 w:0) + /// Proof: `CollatorSelection::DesiredCandidates` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:97 w:97) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `r` is `[1, 100]`. + /// The range of component `c` is `[1, 100]`. fn new_session(r: u32, c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `22815 + c * (97 ±0) + r * (116 ±0)` - // Estimated: `49487 + c * (2519 ±0) + r * (2602 ±0)` - // Minimum execution time: 16_765_000 picoseconds. - Weight::from_parts(16_997_000, 0) - .saturating_add(Weight::from_parts(0, 49487)) - // Standard Error: 860_677 - .saturating_add(Weight::from_parts(30_463_094, 0).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `2143 + c * (97 ±0) + r * (112 ±0)` + // Estimated: `6287 + c * (2519 ±0) + r * (2603 ±0)` + // Minimum execution time: 15_761_000 picoseconds. + Weight::from_parts(16_078_000, 0) + .saturating_add(Weight::from_parts(0, 6287)) + // Standard Error: 270_522 + .saturating_add(Weight::from_parts(11_903_266, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(c.into()))) .saturating_add(T::DbWeight::get().writes(1)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(c.into()))) .saturating_add(Weight::from_parts(0, 2519).saturating_mul(c.into())) - .saturating_add(Weight::from_parts(0, 2602).saturating_mul(r.into())) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(r.into())) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs index 651f27e10e5c7b5d941d2bec5197fc06ed035fda..e9cdc2478766de88bfd3cc8765ba472bac61db30 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_message_queue.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -14,6 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . +//! Autogenerated weights for `pallet_message_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/production/polkadot-parachain +// benchmark +// pallet +// --chain=coretime-westend-dev +// --wasm-execution=compiled +// --pallet=pallet_message_queue +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] @@ -22,134 +47,140 @@ use frame_support::{traits::Get, weights::Weight}; use core::marker::PhantomData; +/// Weight functions for `pallet_message_queue`. pub struct WeightInfo(PhantomData); impl pallet_message_queue::WeightInfo for WeightInfo { - /// Storage: MessageQueue ServiceHead (r:1 w:0) - /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) - /// Storage: MessageQueue BookStateFor (r:2 w:2) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:0) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::BookStateFor` (r:2 w:2) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn ready_ring_knit() -> Weight { // Proof Size summary in bytes: - // Measured: `189` - // Estimated: `7534` - // Minimum execution time: 11_446_000 picoseconds. - Weight::from_parts(11_446_000, 0) - .saturating_add(Weight::from_parts(0, 7534)) + // Measured: `223` + // Estimated: `6044` + // Minimum execution time: 10_918_000 picoseconds. + Weight::from_parts(11_224_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: MessageQueue BookStateFor (r:2 w:2) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue ServiceHead (r:1 w:1) - /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:2 w:2) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) fn ready_ring_unknit() -> Weight { // Proof Size summary in bytes: - // Measured: `184` - // Estimated: `7534` - // Minimum execution time: 10_613_000 picoseconds. - Weight::from_parts(10_613_000, 0) - .saturating_add(Weight::from_parts(0, 7534)) + // Measured: `218` + // Estimated: `6044` + // Minimum execution time: 9_649_000 picoseconds. + Weight::from_parts(10_056_000, 0) + .saturating_add(Weight::from_parts(0, 6044)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn service_queue_base() -> Weight { // Proof Size summary in bytes: // Measured: `6` // Estimated: `3517` - // Minimum execution time: 4_854_000 picoseconds. - Weight::from_parts(4_854_000, 0) + // Minimum execution time: 3_134_000 picoseconds. + Weight::from_parts(3_197_000, 0) .saturating_add(Weight::from_parts(0, 3517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn service_page_base_completion() -> Weight { // Proof Size summary in bytes: // Measured: `72` // Estimated: `69050` - // Minimum execution time: 5_748_000 picoseconds. - Weight::from_parts(5_748_000, 0) + // Minimum execution time: 4_915_000 picoseconds. + Weight::from_parts(5_127_000, 0) .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn service_page_base_no_completion() -> Weight { // Proof Size summary in bytes: // Measured: `72` // Estimated: `69050` - // Minimum execution time: 6_136_000 picoseconds. - Weight::from_parts(6_136_000, 0) + // Minimum execution time: 5_011_000 picoseconds. + Weight::from_parts(5_150_000, 0) .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `MessageQueue::BookStateFor` (r:0 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:0 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn service_page_item() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 59_505_000 picoseconds. - Weight::from_parts(59_505_000, 0) + // Minimum execution time: 168_806_000 picoseconds. + Weight::from_parts(170_795_000, 0) .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: MessageQueue ServiceHead (r:1 w:1) - /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen) - /// Storage: MessageQueue BookStateFor (r:1 w:0) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: `MessageQueue::ServiceHead` (r:1 w:1) + /// Proof: `MessageQueue::ServiceHead` (`max_values`: Some(1), `max_size`: Some(5), added: 500, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:0) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) fn bump_service_head() -> Weight { // Proof Size summary in bytes: - // Measured: `99` - // Estimated: `5007` - // Minimum execution time: 6_506_000 picoseconds. - Weight::from_parts(6_506_000, 0) - .saturating_add(Weight::from_parts(0, 5007)) + // Measured: `171` + // Estimated: `3517` + // Minimum execution time: 6_413_000 picoseconds. + Weight::from_parts(6_797_000, 0) + .saturating_add(Weight::from_parts(0, 3517)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn reap_page() -> Weight { // Proof Size summary in bytes: // Measured: `65667` - // Estimated: `72567` - // Minimum execution time: 40_646_000 picoseconds. - Weight::from_parts(40_646_000, 0) - .saturating_add(Weight::from_parts(0, 72567)) + // Estimated: `69050` + // Minimum execution time: 52_734_000 picoseconds. + Weight::from_parts(54_106_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn execute_overweight_page_removed() -> Weight { // Proof Size summary in bytes: // Measured: `65667` - // Estimated: `72567` - // Minimum execution time: 51_424_000 picoseconds. - Weight::from_parts(51_424_000, 0) - .saturating_add(Weight::from_parts(0, 72567)) + // Estimated: `69050` + // Minimum execution time: 68_400_000 picoseconds. + Weight::from_parts(70_336_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: MessageQueue BookStateFor (r:1 w:1) - /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) - /// Storage: MessageQueue Pages (r:1 w:1) - /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen) + /// Storage: `MessageQueue::BookStateFor` (r:1 w:1) + /// Proof: `MessageQueue::BookStateFor` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`) + /// Storage: `MessageQueue::Pages` (r:1 w:1) + /// Proof: `MessageQueue::Pages` (`max_values`: None, `max_size`: Some(65585), added: 68060, mode: `MaxEncodedLen`) fn execute_overweight_page_updated() -> Weight { // Proof Size summary in bytes: // Measured: `65667` - // Estimated: `72567` - // Minimum execution time: 81_153_000 picoseconds. - Weight::from_parts(81_153_000, 0) - .saturating_add(Weight::from_parts(0, 72567)) + // Estimated: `69050` + // Minimum execution time: 109_496_000 picoseconds. + Weight::from_parts(111_632_000, 0) + .saturating_add(Weight::from_parts(0, 69050)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } -} \ No newline at end of file +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs index 4130e05bf7c4233b5dfdd6fcf0df1295ce77db61..1aaf3f4a6fb9dd88f83042915080b08bb735e664 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_multisig +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -53,110 +55,110 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_337_000 picoseconds. - Weight::from_parts(11_960_522, 0) + // Minimum execution time: 11_938_000 picoseconds. + Weight::from_parts(13_021_007, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 9 - .saturating_add(Weight::from_parts(504, 0).saturating_mul(z.into())) + // Standard Error: 4 + .saturating_add(Weight::from_parts(482, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_128_000 picoseconds. - Weight::from_parts(35_215_592, 0) + // Minimum execution time: 37_643_000 picoseconds. + Weight::from_parts(27_088_068, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 429 - .saturating_add(Weight::from_parts(65_959, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_230, 0).saturating_mul(z.into())) + // Standard Error: 828 + .saturating_add(Weight::from_parts(123_693, 0).saturating_mul(s.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 26_878_000 picoseconds. - Weight::from_parts(21_448_577, 0) + // Minimum execution time: 25_825_000 picoseconds. + Weight::from_parts(15_698_835, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 354 - .saturating_add(Weight::from_parts(60_286, 0).saturating_mul(s.into())) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_236, 0).saturating_mul(z.into())) + // Standard Error: 568 + .saturating_add(Weight::from_parts(111_928, 0).saturating_mul(s.into())) + // Standard Error: 5 + .saturating_add(Weight::from_parts(1_421, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `388 + s * (33 ±0)` + // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_716_000 picoseconds. - Weight::from_parts(38_332_947, 0) + // Minimum execution time: 43_587_000 picoseconds. + Weight::from_parts(29_740_539, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 554 - .saturating_add(Weight::from_parts(81_026, 0).saturating_mul(s.into())) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_265, 0).saturating_mul(z.into())) + // Standard Error: 771 + .saturating_add(Weight::from_parts(154_861, 0).saturating_mul(s.into())) + // Standard Error: 7 + .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_089_000 picoseconds. - Weight::from_parts(33_664_508, 0) + // Minimum execution time: 24_966_000 picoseconds. + Weight::from_parts(25_879_458, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 487 - .saturating_add(Weight::from_parts(67_443, 0).saturating_mul(s.into())) + // Standard Error: 777 + .saturating_add(Weight::from_parts(122_823, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 18_631_000 picoseconds. - Weight::from_parts(19_909_964, 0) + // Minimum execution time: 14_450_000 picoseconds. + Weight::from_parts(14_607_858, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 434 - .saturating_add(Weight::from_parts(62_989, 0).saturating_mul(s.into())) + // Standard Error: 471 + .saturating_add(Weight::from_parts(107_007, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_486_000 picoseconds. - Weight::from_parts(34_303_784, 0) + // Minimum execution time: 26_861_000 picoseconds. + Weight::from_parts(27_846_825, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 585 - .saturating_add(Weight::from_parts(69_979, 0).saturating_mul(s.into())) + // Standard Error: 714 + .saturating_add(Weight::from_parts(116_914, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs index d132ef17bbdb2295dcd4ee812ab62ae51fd6ff3a..b0d993d68a6a4e824c6050a3edef87870438e6b2 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_session` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_session +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_session.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,31 +50,31 @@ use core::marker::PhantomData; /// Weight functions for `pallet_session`. pub struct WeightInfo(PhantomData); impl pallet_session::WeightInfo for WeightInfo { - /// Storage: Session NextKeys (r:1 w:1) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: Session KeyOwner (r:1 w:1) - /// Proof Skipped: Session KeyOwner (max_values: None, max_size: None, mode: Measured) + /// Storage: `Session::NextKeys` (r:1 w:1) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Session::KeyOwner` (r:1 w:1) + /// Proof: `Session::KeyOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_keys() -> Weight { // Proof Size summary in bytes: - // Measured: `297` - // Estimated: `3762` - // Minimum execution time: 17_353_000 picoseconds. - Weight::from_parts(18_005_000, 0) - .saturating_add(Weight::from_parts(0, 3762)) + // Measured: `271` + // Estimated: `3736` + // Minimum execution time: 15_149_000 picoseconds. + Weight::from_parts(16_053_000, 0) + .saturating_add(Weight::from_parts(0, 3736)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Session NextKeys (r:1 w:1) - /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) - /// Storage: Session KeyOwner (r:0 w:1) - /// Proof Skipped: Session KeyOwner (max_values: None, max_size: None, mode: Measured) + /// Storage: `Session::NextKeys` (r:1 w:1) + /// Proof: `Session::NextKeys` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Session::KeyOwner` (r:0 w:1) + /// Proof: `Session::KeyOwner` (`max_values`: None, `max_size`: None, mode: `Measured`) fn purge_keys() -> Weight { // Proof Size summary in bytes: - // Measured: `279` - // Estimated: `3744` - // Minimum execution time: 13_039_000 picoseconds. - Weight::from_parts(13_341_000, 0) - .saturating_add(Weight::from_parts(0, 3744)) + // Measured: `243` + // Estimated: `3708` + // Minimum execution time: 11_159_000 picoseconds. + Weight::from_parts(11_504_000, 0) + .saturating_add(Weight::from_parts(0, 3708)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs index 722858a3a4655881cdbedbe8e6cae419baefc190..ad8924d9ce7374c285ddc662c59d860707443bfc 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_timestamp` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_timestamp +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_timestamp.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,16 +50,16 @@ use core::marker::PhantomData; /// Weight functions for `pallet_timestamp`. pub struct WeightInfo(PhantomData); impl pallet_timestamp::WeightInfo for WeightInfo { - /// Storage: Timestamp Now (r:1 w:1) - /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) - /// Storage: Aura CurrentSlot (r:1 w:0) - /// Proof: Aura CurrentSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: `Timestamp::Now` (r:1 w:1) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Aura::CurrentSlot` (r:1 w:0) + /// Proof: `Aura::CurrentSlot` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) fn set() -> Weight { // Proof Size summary in bytes: - // Measured: `49` + // Measured: `86` // Estimated: `1493` - // Minimum execution time: 7_986_000 picoseconds. - Weight::from_parts(8_134_000, 0) + // Minimum execution time: 5_552_000 picoseconds. + Weight::from_parts(5_821_000, 0) .saturating_add(Weight::from_parts(0, 1493)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -66,8 +68,8 @@ impl pallet_timestamp::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `57` // Estimated: `0` - // Minimum execution time: 3_257_000 picoseconds. - Weight::from_parts(3_366_000, 0) + // Minimum execution time: 2_848_000 picoseconds. + Weight::from_parts(2_953_000, 0) .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs index dacd469ebb7ab62eb7fcd7740bf6bf230aace7ee..0f5340843bd6481da855cb403b2b7d6442ed0a52 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_utility` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_utility +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_utility.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -53,18 +55,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_697_000 picoseconds. - Weight::from_parts(11_859_145, 0) + // Minimum execution time: 3_721_000 picoseconds. + Weight::from_parts(7_071_852, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3_146 - .saturating_add(Weight::from_parts(4_300_555, 0).saturating_mul(c.into())) + // Standard Error: 746 + .saturating_add(Weight::from_parts(2_767_352, 0).saturating_mul(c.into())) } fn as_derivative() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_979_000 picoseconds. - Weight::from_parts(5_066_000, 0) + // Minimum execution time: 3_631_000 picoseconds. + Weight::from_parts(3_836_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -72,18 +74,18 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_741_000 picoseconds. - Weight::from_parts(15_928_547, 0) + // Minimum execution time: 3_817_000 picoseconds. + Weight::from_parts(2_683_003, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3_310 - .saturating_add(Weight::from_parts(4_527_996, 0).saturating_mul(c.into())) + // Standard Error: 782 + .saturating_add(Weight::from_parts(3_059_987, 0).saturating_mul(c.into())) } fn dispatch_as() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_717_000 picoseconds. - Weight::from_parts(8_909_000, 0) + // Minimum execution time: 5_463_000 picoseconds. + Weight::from_parts(5_701_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// The range of component `c` is `[0, 1000]`. @@ -91,10 +93,10 @@ impl pallet_utility::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_814_000 picoseconds. - Weight::from_parts(13_920_831, 0) + // Minimum execution time: 3_771_000 picoseconds. + Weight::from_parts(5_714_929, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 7_605 - .saturating_add(Weight::from_parts(4_306_193, 0).saturating_mul(c.into())) + // Standard Error: 740 + .saturating_add(Weight::from_parts(2_800_888, 0).saturating_mul(c.into())) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs index d96ee43463a316d3b8a0c5c8351045d7c340cd13..d821a581b0dd7bae506f98a52bd28086b9d3c450 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,26 +16,28 @@ //! Autogenerated weights for `pallet_xcm` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/westend-parachain +// ./target/production/polkadot-parachain // benchmark // pallet // --chain=coretime-westend-dev -// --execution=wasm // --wasm-execution=compiled // --pallet=pallet_xcm +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --steps=50 // --repeat=20 // --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,39 +50,50 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 25_783_000 picoseconds. - Weight::from_parts(26_398_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 17_946_000 picoseconds. + Weight::from_parts(18_398_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: ParachainInfo ParachainId (r:1 w:0) - /// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `32` - // Estimated: `1489` - // Minimum execution time: 25_511_000 picoseconds. - Weight::from_parts(26_120_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 47_982_000 picoseconds. + Weight::from_parts(49_215_000, 0) + .saturating_add(Weight::from_parts(0, 3571)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Benchmark Override (r:0 w:0) - /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + /// Storage: `Benchmark::Override` (r:0 w:0) + /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -89,18 +102,18 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `Benchmark::Override` (r:0 w:0) + /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `496` - // Estimated: `6208` - // Minimum execution time: 146_932_000 picoseconds. - Weight::from_parts(153_200_000, 0) - .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(7)) + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. + Weight::from_parts(18_446_744_073_709_551_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Benchmark Override (r:0 w:0) - /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) + /// Storage: `Benchmark::Override` (r:0 w:0) + /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` @@ -109,189 +122,189 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: PolkadotXcm SupportedVersion (r:0 w:1) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_707_000 picoseconds. - Weight::from_parts(9_874_000, 0) + // Minimum execution time: 6_042_000 picoseconds. + Weight::from_parts(6_257_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: PolkadotXcm SafeXcmVersion (r:0 w:1) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:0 w:1) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn force_default_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_073_000 picoseconds. - Weight::from_parts(3_183_000, 0) + // Minimum execution time: 1_845_000 picoseconds. + Weight::from_parts(1_993_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: PolkadotXcm VersionNotifiers (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionNotifiers (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm QueryCounter (r:1 w:1) - /// Proof Skipped: PolkadotXcm QueryCounter (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm Queries (r:0 w:1) - /// Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) + /// Proof: `PolkadotXcm::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::Queries` (r:0 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 30_999_000 picoseconds. - Weight::from_parts(31_641_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 24_062_000 picoseconds. + Weight::from_parts(24_666_000, 0) + .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } - /// Storage: PolkadotXcm VersionNotifiers (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionNotifiers (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm Queries (r:0 w:1) - /// Proof Skipped: PolkadotXcm Queries (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::Queries` (r:0 w:1) + /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `220` - // Estimated: `3685` - // Minimum execution time: 33_036_000 picoseconds. - Weight::from_parts(33_596_000, 0) - .saturating_add(Weight::from_parts(0, 3685)) + // Measured: `292` + // Estimated: `3757` + // Minimum execution time: 26_486_000 picoseconds. + Weight::from_parts(27_528_000, 0) + .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: PolkadotXcm XcmExecutionSuspended (r:0 w:1) - /// Proof Skipped: PolkadotXcm XcmExecutionSuspended (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) + /// Proof: `PolkadotXcm::XcmExecutionSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn force_suspension() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_035_000 picoseconds. - Weight::from_parts(3_154_000, 0) + // Minimum execution time: 1_881_000 picoseconds. + Weight::from_parts(2_008_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: PolkadotXcm SupportedVersion (r:4 w:2) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `95` - // Estimated: `10985` - // Minimum execution time: 14_805_000 picoseconds. - Weight::from_parts(15_120_000, 0) - .saturating_add(Weight::from_parts(0, 10985)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `89` + // Estimated: `13454` + // Minimum execution time: 15_971_000 picoseconds. + Weight::from_parts(16_455_000, 0) + .saturating_add(Weight::from_parts(0, 13454)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: PolkadotXcm VersionNotifiers (r:4 w:2) - /// Proof Skipped: PolkadotXcm VersionNotifiers (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `99` - // Estimated: `10989` - // Minimum execution time: 14_572_000 picoseconds. - Weight::from_parts(14_909_000, 0) - .saturating_add(Weight::from_parts(0, 10989)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `93` + // Estimated: `13458` + // Minimum execution time: 16_603_000 picoseconds. + Weight::from_parts(17_037_000, 0) + .saturating_add(Weight::from_parts(0, 13458)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:5 w:0) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 15_341_000 picoseconds. - Weight::from_parts(15_708_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15946` + // Minimum execution time: 17_821_000 picoseconds. + Weight::from_parts(18_200_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(6)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:2 w:1) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `106` - // Estimated: `6046` - // Minimum execution time: 27_840_000 picoseconds. - Weight::from_parts(28_248_000, 0) - .saturating_add(Weight::from_parts(0, 6046)) + // Measured: `142` + // Estimated: `6082` + // Minimum execution time: 23_878_000 picoseconds. + Weight::from_parts(24_721_000, 0) + .saturating_add(Weight::from_parts(0, 6082)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:3 w:0) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: // Measured: `136` - // Estimated: `8551` - // Minimum execution time: 8_245_000 picoseconds. - Weight::from_parts(8_523_000, 0) - .saturating_add(Weight::from_parts(0, 8551)) - .saturating_add(T::DbWeight::get().reads(3)) + // Estimated: `11026` + // Minimum execution time: 10_566_000 picoseconds. + Weight::from_parts(11_053_000, 0) + .saturating_add(Weight::from_parts(0, 11026)) + .saturating_add(T::DbWeight::get().reads(4)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:4 w:2) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `106` - // Estimated: `10996` - // Minimum execution time: 14_780_000 picoseconds. - Weight::from_parts(15_173_000, 0) - .saturating_add(Weight::from_parts(0, 10996)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `100` + // Estimated: `13465` + // Minimum execution time: 16_020_000 picoseconds. + Weight::from_parts(16_619_000, 0) + .saturating_add(Weight::from_parts(0, 13465)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: PolkadotXcm VersionNotifyTargets (r:4 w:2) - /// Proof Skipped: PolkadotXcm VersionNotifyTargets (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm SupportedVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SupportedVersion (max_values: None, max_size: None, mode: Measured) - /// Storage: PolkadotXcm VersionDiscoveryQueue (r:1 w:1) - /// Proof Skipped: PolkadotXcm VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: PolkadotXcm SafeXcmVersion (r:1 w:0) - /// Proof Skipped: PolkadotXcm SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem HostConfiguration (r:1 w:0) - /// Proof Skipped: ParachainSystem HostConfiguration (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ParachainSystem PendingUpwardMessages (r:1 w:1) - /// Proof Skipped: ParachainSystem PendingUpwardMessages (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `112` - // Estimated: `11002` - // Minimum execution time: 33_422_000 picoseconds. - Weight::from_parts(34_076_000, 0) - .saturating_add(Weight::from_parts(0, 11002)) - .saturating_add(T::DbWeight::get().reads(9)) + // Measured: `142` + // Estimated: `13507` + // Minimum execution time: 32_136_000 picoseconds. + Weight::from_parts(32_610_000, 0) + .saturating_add(Weight::from_parts(0, 13507)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -300,11 +313,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn new_query() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `1554` - // Minimum execution time: 4_512_000 picoseconds. - Weight::from_parts(4_671_000, 0) - .saturating_add(Weight::from_parts(0, 1554)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 3_336_000 picoseconds. + Weight::from_parts(3_434_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -312,11 +325,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn take_response() -> Weight { // Proof Size summary in bytes: - // Measured: `7706` - // Estimated: `11171` - // Minimum execution time: 26_473_000 picoseconds. - Weight::from_parts(26_960_000, 0) - .saturating_add(Weight::from_parts(0, 11171)) + // Measured: `7669` + // Estimated: `11134` + // Minimum execution time: 23_977_000 picoseconds. + Weight::from_parts(24_413_000, 0) + .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs index a14da7c7a38a54938bb390d5d2109816162fecf7..99af88812da2be05bd9273585ea7d186be9f8b90 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs @@ -196,7 +196,7 @@ impl XcmWeightInfo for CoretimeWestendXcmWeight { XcmGeneric::::clear_transact_status() } fn universal_origin(_: &Junction) -> Weight { - XcmGeneric::::universal_origin() + Weight::MAX } fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { Weight::MAX diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index eaf07aac52cefa88f524e6f3a2180ab9faf2b088..6f5a52de98c39fb7ff5de96c41652be063f56a74 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -1,41 +1,44 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// 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. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-vmdtonbz-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: Compiled, CHAIN: Some("asset-hub-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 // Executed Command: -// target/production/polkadot-parachain +// ./target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 -// --extrinsic=* +// --template=./cumulus/templates/xcm-bench-template.hbs +// --chain=coretime-westend-dev // --wasm-execution=compiled -// --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm_benchmarks::fungible -// --chain=asset-hub-westend-dev +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json // --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/ +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -53,8 +56,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 20_295_000 picoseconds. - Weight::from_parts(21_142_000, 3593) + // Minimum execution time: 19_401_000 picoseconds. + Weight::from_parts(19_768_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -64,17 +67,15 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `6196` - // Minimum execution time: 42_356_000 picoseconds. - Weight::from_parts(43_552_000, 6196) + // Minimum execution time: 42_452_000 picoseconds. + Weight::from_parts(43_126_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: `System::Account` (r:3 w:3) + // Storage: `System::Account` (r:2 w:2) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -87,54 +88,49 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `8799` - // Minimum execution time: 85_553_000 picoseconds. - Weight::from_parts(87_177_000, 8799) - .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `207` + // Estimated: `6196` + // Minimum execution time: 58_090_000 picoseconds. + Weight::from_parts(59_502_000, 6196) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `Benchmark::Override` (r:0 w:0) + // Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn reserve_asset_deposited() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 6_166_000 picoseconds. - Weight::from_parts(6_352_000, 1489) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. + Weight::from_parts(18_446_744_073_709_551_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_reserve_withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 184_462_000 picoseconds. - Weight::from_parts(189_593_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 23_569_000 picoseconds. + Weight::from_parts(24_598_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } pub fn receive_teleported_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_018_000 picoseconds. - Weight::from_parts(3_098_000, 0) + // Minimum execution time: 2_546_000 picoseconds. + Weight::from_parts(2_674_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) @@ -142,59 +138,53 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 18_583_000 picoseconds. - Weight::from_parts(19_057_000, 3593) + // Minimum execution time: 16_889_000 picoseconds. + Weight::from_parts(17_350_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `6196` - // Minimum execution time: 56_666_000 picoseconds. - Weight::from_parts(58_152_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3593` + // Minimum execution time: 43_964_000 picoseconds. + Weight::from_parts(45_293_000, 3593) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:1 w:1) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_teleport() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 44_197_000 picoseconds. - Weight::from_parts(45_573_000, 3610) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 20_704_000 picoseconds. + Weight::from_parts(21_266_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index fc196abea0f5e61d746760e2b2bf5a7d8d0a476b..74254814bcafc7f67dd20d8fa5dcd8da4337f782 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -1,41 +1,44 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// 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. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-10-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-vmdtonbz-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: Compiled, CHAIN: Some("asset-hub-westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 // Executed Command: -// target/production/polkadot-parachain +// ./target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 -// --extrinsic=* +// --template=./cumulus/templates/xcm-bench-template.hbs +// --chain=coretime-westend-dev // --wasm-execution=compiled -// --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=pallet_xcm_benchmarks::generic -// --chain=asset-hub-westend-dev +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --steps=50 +// --repeat=20 +// --json // --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/ +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -49,128 +52,120 @@ pub struct WeightInfo(PhantomData); impl WeightInfo { // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_holding() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 415_033_000 picoseconds. - Weight::from_parts(429_573_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 22_424_000 picoseconds. + Weight::from_parts(23_208_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } pub fn buy_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_193_000 picoseconds. - Weight::from_parts(3_620_000, 0) + // Minimum execution time: 1_194_000 picoseconds. + Weight::from_parts(1_306_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn query_response() -> Weight { // Proof Size summary in bytes: - // Measured: `103` - // Estimated: `3568` - // Minimum execution time: 8_045_000 picoseconds. - Weight::from_parts(8_402_000, 3568) + // Measured: `32` + // Estimated: `3497` + // Minimum execution time: 6_359_000 picoseconds. + Weight::from_parts(6_585_000, 3497) .saturating_add(T::DbWeight::get().reads(1)) } pub fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_827_000 picoseconds. - Weight::from_parts(10_454_000, 0) + // Minimum execution time: 6_297_000 picoseconds. + Weight::from_parts(6_661_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_330_000 picoseconds. - Weight::from_parts(3_677_000, 0) + // Minimum execution time: 1_778_000 picoseconds. + Weight::from_parts(1_923_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_947_000 picoseconds. - Weight::from_parts(2_083_000, 0) + // Minimum execution time: 1_212_000 picoseconds. + Weight::from_parts(1_314_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_915_000 picoseconds. - Weight::from_parts(1_993_000, 0) + // Minimum execution time: 1_165_000 picoseconds. + Weight::from_parts(1_247_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_918_000 picoseconds. - Weight::from_parts(2_048_000, 0) + // Minimum execution time: 1_173_000 picoseconds. + Weight::from_parts(1_275_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_683_000 picoseconds. - Weight::from_parts(3_064_000, 0) + // Minimum execution time: 1_247_000 picoseconds. + Weight::from_parts(1_332_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_893_000 picoseconds. - Weight::from_parts(2_159_000, 0) + // Minimum execution time: 1_170_000 picoseconds. + Weight::from_parts(1_237_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_error() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 53_116_000 picoseconds. - Weight::from_parts(54_154_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 19_872_000 picoseconds. + Weight::from_parts(20_453_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) // Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn claim_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `160` - // Estimated: `3625` - // Minimum execution time: 12_381_000 picoseconds. - Weight::from_parts(12_693_000, 3625) + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 9_105_000 picoseconds. + Weight::from_parts(9_365_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,13 +173,11 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_933_000 picoseconds. - Weight::from_parts(1_983_000, 0) + // Minimum execution time: 1_228_000 picoseconds. + Weight::from_parts(1_293_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -197,11 +190,11 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn subscribe_version() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 24_251_000 picoseconds. - Weight::from_parts(24_890_000, 3610) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `74` + // Estimated: `3539` + // Minimum execution time: 19_535_000 picoseconds. + Weight::from_parts(20_139_000, 3539) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:0 w:1) @@ -210,145 +203,127 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_850_000 picoseconds. - Weight::from_parts(4_082_000, 0) + // Minimum execution time: 3_158_000 picoseconds. + Weight::from_parts(3_275_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 112_248_000 picoseconds. - Weight::from_parts(124_454_000, 0) + // Minimum execution time: 1_539_000 picoseconds. + Weight::from_parts(1_607_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_457_000 picoseconds. - Weight::from_parts(12_060_000, 0) + // Minimum execution time: 1_317_000 picoseconds. + Weight::from_parts(1_427_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_959_000 picoseconds. - Weight::from_parts(2_076_000, 0) + // Minimum execution time: 1_176_000 picoseconds. + Weight::from_parts(1_250_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_920_000 picoseconds. - Weight::from_parts(1_994_000, 0) + // Minimum execution time: 1_202_000 picoseconds. + Weight::from_parts(1_279_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_149_000 picoseconds. - Weight::from_parts(2_394_000, 0) + // Minimum execution time: 1_411_000 picoseconds. + Weight::from_parts(1_463_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn query_pallet() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 58_011_000 picoseconds. - Weight::from_parts(59_306_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 22_991_000 picoseconds. + Weight::from_parts(23_820_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } pub fn expect_pallet() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_031_000 picoseconds. - Weight::from_parts(5_243_000, 0) + // Minimum execution time: 3_534_000 picoseconds. + Weight::from_parts(3_708_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) - // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:2 w:2) - // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_transact_status() -> Weight { // Proof Size summary in bytes: - // Measured: `246` - // Estimated: `6196` - // Minimum execution time: 53_078_000 picoseconds. - Weight::from_parts(54_345_000, 6196) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `106` + // Estimated: `3571` + // Minimum execution time: 20_025_000 picoseconds. + Weight::from_parts(20_463_000, 3571) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } pub fn clear_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_936_000 picoseconds. - Weight::from_parts(2_002_000, 0) + // Minimum execution time: 1_213_000 picoseconds. + Weight::from_parts(1_290_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_855_000 picoseconds. - Weight::from_parts(1_950_000, 0) + // Minimum execution time: 1_207_000 picoseconds. + Weight::from_parts(1_265_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_882_000 picoseconds. - Weight::from_parts(1_977_000, 0) - } - // Storage: `ParachainInfo::ParachainId` (r:1 w:0) - // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - pub fn universal_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `1489` - // Minimum execution time: 3_912_000 picoseconds. - Weight::from_parts(4_167_000, 1489) - .saturating_add(T::DbWeight::get().reads(1)) + // Minimum execution time: 1_195_000 picoseconds. + Weight::from_parts(1_231_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_911_000 picoseconds. - Weight::from_parts(1_971_000, 0) + // Minimum execution time: 1_182_000 picoseconds. + Weight::from_parts(1_265_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_990_000 picoseconds. - Weight::from_parts(2_076_000, 0) + // Minimum execution time: 1_165_000 picoseconds. + Weight::from_parts(1_252_000, 0) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs index e57a46877764381b5156614af20488368be7845c..44049adf02711bd3b828f98bc3b90ab330e19317 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -15,11 +15,12 @@ // along with Cumulus. If not, see . use super::{ - AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, FeeAssetId, ParachainInfo, + AccountId, AllPalletsWithSystem, Balances, BaseDeliveryFee, Broker, FeeAssetId, ParachainInfo, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, WeightToFee, XcmpQueue, }; use frame_support::{ + pallet_prelude::PalletInfoAccess, parameter_types, traits::{ConstU32, Contains, Equals, Everything, Nothing}, }; @@ -37,25 +38,26 @@ use polkadot_parachain_primitives::primitives::Sibling; use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, IsConcrete, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + NonFungibleAdapter, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; parameter_types! { - pub const WndRelayLocation: Location = Location::parent(); + pub const TokenRelayLocation: Location = Location::parent(); pub const RelayNetwork: Option = Some(NetworkId::Westend); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = [GlobalConsensus(RelayNetwork::get().unwrap()), Parachain(ParachainInfo::parachain_id().into())].into(); + pub BrokerPalletLocation: Location = + PalletInstance(::index() as u8).into(); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; pub FellowshipLocation: Location = Location::new(1, Parachain(1001)); @@ -75,12 +77,11 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, + IsConcrete, // Do a simple punn to convert an `AccountId32` `Location` into a native chain // `AccountId`: LocationToAccountId, @@ -90,6 +91,23 @@ pub type CurrencyTransactor = CurrencyAdapter< (), >; +/// Means for transacting coretime regions on this chain. +pub type RegionTransactor = NonFungibleAdapter< + // Use this non-fungible implementation: + Broker, + // This adapter will handle coretime regions from the broker pallet. + IsConcrete, + // Convert an XCM Location into a local account id: + LocationToAccountId, + // Our chain's account ID type (we can't get away without mentioning it explicitly): + AccountId, + // We don't track any teleports. + (), +>; + +/// Means for transacting assets on this chain. +pub type AssetTransactors = (FungibleTransactor, RegionTransactor); + /// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, /// ready for dispatching a transaction with XCM's `Transact`. There is an `OriginKind` that can /// bias the kind of local `Origin` it will become. @@ -152,18 +170,20 @@ impl Contains for SafeCallFilter { pallet_xcm::Call::force_default_xcm_version { .. } ) | RuntimeCall::System( frame_system::Call::set_heap_pages { .. } | - frame_system::Call::set_code { .. } | - frame_system::Call::set_code_without_checks { .. } | - frame_system::Call::authorize_upgrade { .. } | - frame_system::Call::authorize_upgrade_without_checks { .. } | - frame_system::Call::kill_prefix { .. }, + frame_system::Call::set_code { .. } | + frame_system::Call::set_code_without_checks { .. } | + frame_system::Call::authorize_upgrade { .. } | + frame_system::Call::authorize_upgrade_without_checks { .. } | + frame_system::Call::kill_prefix { .. } | + // Should not be in Polkadot/Kusama. Here in order to speed up testing. + frame_system::Call::set_storage { .. }, ) | RuntimeCall::ParachainSystem(..) | RuntimeCall::Timestamp(..) | RuntimeCall::Balances(..) | RuntimeCall::CollatorSelection(..) | - RuntimeCall::Sudo(..) | RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) | - RuntimeCall::XcmpQueue(..) + RuntimeCall::XcmpQueue(..) | + RuntimeCall::Broker(..) ) } } @@ -210,13 +230,13 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = AssetTransactors; type OriginConverter = XcmOriginToTransactDispatchOrigin; - // Coretime chain does not recognize a reserve location for any asset. Users must teleport WND + // Coretime chain does not recognize a reserve location for any asset. Users must teleport ROC // where allowed (e.g. with the Relay Chain). type IsReserve = (); /// Only allow teleportation of WND. - type IsTeleporter = ConcreteAssetFromSystem; + type IsTeleporter = ConcreteAssetFromSystem; type UniversalLocation = UniversalLocation; type Barrier = Barrier; type Weigher = WeightInfoBounds< @@ -224,8 +244,13 @@ impl xcm_executor::Config for XcmConfig { RuntimeCall, MaxInstructions, >; - type Trader = - UsingComponents>; + type Trader = UsingComponents< + WeightToFee, + TokenRelayLocation, + AccountId, + Balances, + ToStakingPot, + >; type ResponseHandler = PolkadotXcm; type AssetTrap = PolkadotXcm; type AssetClaims = PolkadotXcm; diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index ce9c31ba73da49a6fb71396f8ebd23835751fea8..10408aaf39a7611f93178802e17403ed4fd90838 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -87,6 +87,7 @@ use parachains_common::{AccountId, Signature}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; pub use sp_runtime::{Perbill, Permill}; +use testnet_parachains_constants::westend::consensus::*; impl_opaque_keys! { pub struct SessionKeys { @@ -99,7 +100,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("glutton-westend"), impl_name: create_runtime_str!("glutton-westend"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -118,29 +119,6 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used /// by Operational extrinsics. const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -/// We allow for .5 seconds of compute with a 12 second average block time. -const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( - WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), - cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, -); - -/// Maximum number of blocks simultaneously accepted by the Runtime, not yet included -/// into the relay chain. -const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; -/// How many parachain blocks are processed by the relay chain per parent. Limits the -/// number of blocks authored per slot. -const BLOCK_PROCESSING_VELOCITY: u32 = 2; -/// Relay chain slot duration, in milliseconds. -const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; - -/// This determines the average expected block time that we are targeting. -/// Blocks will be produced at a minimum duration defined by `SLOT_DURATION`. -/// `SLOT_DURATION` is picked up by `pallet_timestamp` which is in turn picked -/// up by `pallet_aura` to implement `fn slot_duration()`. -/// -/// Change this to adjust the block time. -pub const MILLISECS_PER_BLOCK: u64 = 6000; -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; parameter_types! { pub const BlockHashCount: BlockNumber = 4096; diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index efb214fd491c6224ffe43c286f6d9c7525eeb1e1..c0b8fb7636b5898861c0b12912c58fbf0d43f924 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -13,9 +13,9 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } enumflags2 = { version = "0.7.7" } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -66,6 +66,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -82,6 +83,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "enumflags2/std", diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 2074b81c2cc5ad7834b5dbe5daa5fa436760b23f..90c398917695a0ef1150e42c6b61430bbb6de783 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -22,7 +22,7 @@ pub mod people; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -44,7 +44,7 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, - HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::{identity_migrator, BlockHashCount, SlowAdjustingFeeUpdate}; use sp_api::impl_runtime_apis; @@ -63,7 +63,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::latest::prelude::BodyId; use xcm_config::{ @@ -100,7 +100,10 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (); +pub type Migrations = ( + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -123,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("people-rococo"), impl_name: create_runtime_str!("people-rococo"), authoring_version: 1, - spec_version: 1_006_002, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, @@ -184,6 +187,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -243,16 +249,18 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + parameter_types! { pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; @@ -328,9 +336,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -440,6 +448,7 @@ mod benches { [frame_system, SystemBench::] [pallet_balances, Balances] [pallet_identity, Identity] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_session, SessionBench::] [pallet_utility, Utility] @@ -447,6 +456,7 @@ mod benches { // Polkadot [polkadot_runtime_common::identity_migrator, IdentityMigrator] // Cumulus + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_collator_selection, CollatorSelection] // XCM @@ -459,7 +469,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -467,6 +477,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION @@ -667,6 +686,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsiscsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -675,7 +700,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between People and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs index c7408b3fa84baed1bd03f8a7bb00077f98698be3..311128a17ca9c16f18adf60e5dbf383161dc3bab 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/xcm_config.rs @@ -27,24 +27,23 @@ use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, xcm_config::{ - AllSiblingSystemParachains, ConcreteAssetFromSystem, RelayOrOtherSystemParachains, + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, }, TREASURY_PALLET_ID, }; use polkadot_parachain_primitives::primitives::Sibling; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, HashedDescription, - IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -97,8 +96,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -150,13 +148,6 @@ impl Contains for ParentOrParentsPlurality { } } -pub struct ParentOrSiblings; -impl Contains for ParentOrSiblings { - fn contains(location: &Location) -> bool { - matches!(location.unpack(), (1, []) | (1, [Parachain(_)])) - } -} - /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly /// account for proof size weights. /// @@ -222,7 +213,7 @@ pub type Barrier = TrailingSetTopicAsId< // Parent and its pluralities (i.e. governance bodies) get free execution. AllowExplicitUnpaidExecutionFrom, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, @@ -244,7 +235,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // People chain does not recognize a reserve location for any asset. Users must teleport ROC // where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index b5d1d6d43d7f9c6c6511aa46f8151666f7d56a67..e87e825a34e8d9ad8c7f41883f3cfbb826064b06 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -13,9 +13,9 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } enumflags2 = { version = "0.7.7" } hex-literal = { version = "0.4.1" } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } # Substrate frame-benchmarking = { path = "../../../../../substrate/frame/benchmarking", default-features = false, optional = true } @@ -66,6 +66,7 @@ cumulus-pallet-parachain-system = { path = "../../../../pallets/parachain-system cumulus-pallet-session-benchmarking = { path = "../../../../pallets/session-benchmarking", default-features = false } cumulus-pallet-xcm = { path = "../../../../pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "../../../../pallets/xcmp-queue", default-features = false } +cumulus-primitives-aura = { path = "../../../../primitives/aura", default-features = false } cumulus-primitives-core = { path = "../../../../primitives/core", default-features = false } cumulus-primitives-utility = { path = "../../../../primitives/utility", default-features = false } pallet-collator-selection = { path = "../../../../pallets/collator-selection", default-features = false } @@ -82,6 +83,7 @@ std = [ "cumulus-pallet-session-benchmarking/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", "cumulus-primitives-core/std", "cumulus-primitives-utility/std", "enumflags2/std", diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 4f650fed3cda1a145bc4b9b9fe9288449bdabda1..a904f7c3521be8b7122eb36fc0a1baa053106b38 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -22,7 +22,7 @@ pub mod people; mod weights; pub mod xcm_config; -use cumulus_pallet_parachain_system::RelayNumberStrictlyIncreases; +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; use frame_support::{ construct_runtime, derive_impl, @@ -44,7 +44,7 @@ use parachains_common::{ impls::DealWithFees, message_queue::{NarrowOriginToSibling, ParaIdToSibling}, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, - HOURS, MAXIMUM_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, + NORMAL_DISPATCH_RATIO, }; use polkadot_runtime_common::{identity_migrator, BlockHashCount, SlowAdjustingFeeUpdate}; use sp_api::impl_runtime_apis; @@ -63,7 +63,7 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee}; +use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*}; use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::latest::prelude::BodyId; use xcm_config::{ @@ -100,7 +100,10 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; /// Migrations to apply on runtime upgrade. -pub type Migrations = (); +pub type Migrations = ( + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, +); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -123,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("people-westend"), impl_name: create_runtime_str!("people-westend"), authoring_version: 1, - spec_version: 1_006_001, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 0, @@ -184,6 +187,9 @@ impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; + #[cfg(feature = "experimental")] + type MinimumPeriod = ConstU64<0>; + #[cfg(not(feature = "experimental"))] type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = weights::pallet_timestamp::WeightInfo; } @@ -243,16 +249,18 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type ReservedDmpWeight = ReservedDmpWeight; type XcmpMessageHandler = XcmpQueue; type ReservedXcmpWeight = ReservedXcmpWeight; - type CheckAssociatedRelayNumber = RelayNumberStrictlyIncreases; - type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, - >; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; type WeightInfo = weights::cumulus_pallet_parachain_system::WeightInfo; } +type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, +>; + parameter_types! { pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; @@ -328,9 +336,9 @@ impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<100_000>; - type AllowMultipleBlocksPerSlot = ConstBool; + type AllowMultipleBlocksPerSlot = ConstBool; #[cfg(feature = "experimental")] - type SlotDuration = pallet_aura::MinimumPeriodTimesTwo; + type SlotDuration = ConstU64; } parameter_types! { @@ -440,6 +448,7 @@ mod benches { [frame_system, SystemBench::] [pallet_balances, Balances] [pallet_identity, Identity] + [pallet_message_queue, MessageQueue] [pallet_multisig, Multisig] [pallet_session, SessionBench::] [pallet_utility, Utility] @@ -447,6 +456,7 @@ mod benches { // Polkadot [polkadot_runtime_common::identity_migrator, IdentityMigrator] // Cumulus + [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_collator_selection, CollatorSelection] // XCM @@ -459,7 +469,7 @@ mod benches { impl_runtime_apis! { impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) + sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) } fn authorities() -> Vec { @@ -467,6 +477,15 @@ impl_runtime_apis! { } } + impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { + fn can_build_upon( + included_hash: ::Hash, + slot: cumulus_primitives_aura::Slot, + ) -> bool { + ConsensusHook::can_build_upon(included_hash, slot) + } + } + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION @@ -667,6 +686,12 @@ impl_runtime_apis! { use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsiscsBenchmark; impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn reachable_dest() -> Option { Some(Parent.into()) } @@ -675,7 +700,7 @@ impl_runtime_apis! { // Relay/native token can be teleported between People and Relay. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Parent.into()) }, Parent.into(), diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs index baead5234191594906c9b416aade7e67b9bae081..4b7da91c17e5568bc4ca34f3f4a255f8276893e8 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -27,24 +27,23 @@ use pallet_xcm::XcmPassthrough; use parachains_common::{ impls::ToStakingPot, xcm_config::{ - AllSiblingSystemParachains, ConcreteAssetFromSystem, RelayOrOtherSystemParachains, + AllSiblingSystemParachains, ConcreteAssetFromSystem, ParentRelayOrSiblingParachains, + RelayOrOtherSystemParachains, }, TREASURY_PALLET_ID, }; use polkadot_parachain_primitives::primitives::Sibling; use sp_runtime::traits::AccountIdConversion; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, - DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, HashedDescription, - IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + DenyThenTry, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, + HashedDescription, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, + SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::{traits::WithOriginFilter, XcmExecutor}; @@ -97,8 +96,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting the native currency on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type FungibleTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -150,13 +148,6 @@ impl Contains for ParentOrParentsPlurality { } } -pub struct ParentOrSiblings; -impl Contains for ParentOrSiblings { - fn contains(location: &Location) -> bool { - matches!(location.unpack(), (1, []) | (1, [Parachain(_)])) - } -} - pub struct FellowsPlurality; impl Contains for FellowsPlurality { fn contains(location: &Location) -> bool { @@ -230,7 +221,7 @@ pub type Barrier = TrailingSetTopicAsId< // get free execution. AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, FellowsPlurality)>, // Subscriptions for version tracking are OK. - AllowSubscriptionsFrom, + AllowSubscriptionsFrom, ), UniversalLocation, ConstU32<8>, @@ -252,7 +243,7 @@ pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; - type AssetTransactor = CurrencyTransactor; + type AssetTransactor = FungibleTransactor; type OriginConverter = XcmOriginToTransactDispatchOrigin; // People does not recognize a reserve location for any asset. Users must teleport WND // where allowed (e.g. with the Relay Chain). diff --git a/cumulus/parachains/runtimes/test-utils/Cargo.toml b/cumulus/parachains/runtimes/test-utils/Cargo.toml index a61e05de13fa6dff6e54f583a00683d4a270e429..eda88beb7dabb41bd4075ec5ab6bf8ec2f42d3c8 100644 --- a/cumulus/parachains/runtimes/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/test-utils/Cargo.toml @@ -17,6 +17,7 @@ frame-support = { path = "../../../../substrate/frame/support", default-features frame-system = { path = "../../../../substrate/frame/system", default-features = false } pallet-balances = { path = "../../../../substrate/frame/balances", default-features = false } pallet-session = { path = "../../../../substrate/frame/session", default-features = false } +pallet-timestamp = { path = "../../../../substrate/frame/timestamp", default-features = false } sp-consensus-aura = { path = "../../../../substrate/primitives/consensus/aura", default-features = false } sp-io = { path = "../../../../substrate/primitives/io", default-features = false } sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } @@ -59,6 +60,7 @@ std = [ "pallet-balances/std", "pallet-collator-selection/std", "pallet-session/std", + "pallet-timestamp/std", "pallet-xcm/std", "parachain-info/std", "polkadot-parachain-primitives/std", diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index b4eb57fcb66f412cd697cfd4d6efd1f551ef7eb6..e62daa16a1256fa993a97d82d4f6df96f261018b 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -34,7 +34,7 @@ use polkadot_parachain_primitives::primitives::{ }; use sp_consensus_aura::{SlotDuration, AURA_ENGINE_ID}; use sp_core::{Encode, U256}; -use sp_runtime::{traits::Header, BuildStorage, Digest, DigestItem}; +use sp_runtime::{traits::Header, BuildStorage, Digest, DigestItem, SaturatedConversion}; use xcm::{ latest::{Asset, Location, XcmContext, XcmHash}, prelude::*, @@ -129,6 +129,7 @@ pub trait BasicParachainRuntime: + parachain_info::Config + pallet_collator_selection::Config + cumulus_pallet_parachain_system::Config + + pallet_timestamp::Config { } @@ -140,7 +141,8 @@ where + pallet_xcm::Config + parachain_info::Config + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config, + + cumulus_pallet_parachain_system::Config + + pallet_timestamp::Config, ValidatorIdOf: From>, { } @@ -259,8 +261,10 @@ pub struct RuntimeHelper( ); /// Utility function that advances the chain to the desired block number. /// If an author is provided, that author information is injected to all the blocks in the meantime. -impl - RuntimeHelper +impl< + Runtime: frame_system::Config + cumulus_pallet_parachain_system::Config + pallet_timestamp::Config, + AllPalletsWithoutSystem, + > RuntimeHelper where AccountIdOf: Into<<::RuntimeOrigin as OriginTrait>::AccountId>, @@ -296,6 +300,65 @@ where last_header.expect("run_to_block empty block range") } + pub fn run_to_block_with_finalize(n: u32) -> HeaderFor { + let mut last_header = None; + loop { + let block_number = frame_system::Pallet::::block_number(); + if block_number >= n.into() { + break + } + // Set the new block number and author + let header = frame_system::Pallet::::finalize(); + + let pre_digest = Digest { + logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, block_number.encode())], + }; + frame_system::Pallet::::reset_events(); + + let next_block_number = block_number + 1u32.into(); + frame_system::Pallet::::initialize( + &next_block_number, + &header.hash(), + &pre_digest, + ); + AllPalletsWithoutSystem::on_initialize(next_block_number); + + let parent_head = HeadData(header.encode()); + let sproof_builder = RelayStateSproofBuilder { + para_id: ::SelfParaId::get(), + included_para_head: parent_head.clone().into(), + ..Default::default() + }; + + let (relay_parent_storage_root, relay_chain_state) = + sproof_builder.into_state_root_and_proof(); + let inherent_data = ParachainInherentData { + validation_data: PersistedValidationData { + parent_head, + relay_parent_number: (block_number.saturated_into::() * 2 + 1).into(), + relay_parent_storage_root, + max_pov_size: 100_000_000, + }, + relay_chain_state, + downward_messages: Default::default(), + horizontal_messages: Default::default(), + }; + + let _ = cumulus_pallet_parachain_system::Pallet::::set_validation_data( + Runtime::RuntimeOrigin::none(), + inherent_data, + ); + let _ = pallet_timestamp::Pallet::::set( + Runtime::RuntimeOrigin::none(), + 300_u32.into(), + ); + AllPalletsWithoutSystem::on_finalize(next_block_number); + let header = frame_system::Pallet::::finalize(); + last_header = Some(header); + } + last_header.expect("run_to_block empty block range") + } + pub fn root_origin() -> ::RuntimeOrigin { ::RuntimeOrigin::root() } diff --git a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs index f78bf9877ec2443a9ce3d754a533f3ee6a3cf14a..1c58df189b673af60bc3e4a9ae7391294287e2aa 100644 --- a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs @@ -37,7 +37,8 @@ pub fn change_storage_constant_by_governance_works: From>, StorageConstant: Get, StorageConstantType: Encode + PartialEq + std::fmt::Debug, @@ -107,7 +108,8 @@ pub fn set_storage_keys_by_governance_works( + pallet_xcm::Config + parachain_info::Config + pallet_collator_selection::Config - + cumulus_pallet_parachain_system::Config, + + cumulus_pallet_parachain_system::Config + + pallet_timestamp::Config, ValidatorIdOf: From>, { let mut runtime = ExtBuilder::::default() diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index dab687c527786366ff1901156c0aec58879ccaf4..08e5987d43afd5cf2676e56dee79afa04362ada0 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -20,7 +20,7 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder", [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex-literal = { version = "0.4.1", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } smallvec = "1.11.0" diff --git a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs index 3fad47576fd6cc5a735e4f4be7546a1d8ce73872..ef4f466d48425a615ec0816f0c90d13f4ba82a41 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/xcm_config.rs @@ -45,12 +45,11 @@ use polkadot_runtime_common::impls::ToAuthor; use sp_runtime::traits::Zero; use testnet_parachains_constants::rococo::snowbridge::EthereumNetwork; use xcm::latest::prelude::*; -#[allow(deprecated)] use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, AsPrefixedGeneralIndex, - ConvertedConcreteId, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, - FrameTransactionalProcessor, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, + ConvertedConcreteId, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, + FungibleAdapter, FungiblesAdapter, IsConcrete, LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith, TakeWeightCredit, TrailingSetTopicAsId, @@ -78,8 +77,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -136,7 +134,7 @@ pub type ForeignFungiblesTransactor = FungiblesAdapter< ForeignAssets, // Use this currency when it is a fungible asset matching the given location or name: ForeignAssetsConvertedConcreteId, - // Convert an XCM MultiLocation into a local account id: + // Convert an XCM Location into a local account id: LocationToAccountId, // Our chain's account ID type (we can't get away without mentioning it explicitly): AccountId, diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index c43d79271de717cbe0273edfdc6a7ba10a96aa01..57969d9a4f1882d218fdc97374f52f467f109398 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -84,14 +84,12 @@ use xcm_executor::traits::JustTry; use pallet_xcm::{EnsureXcm, IsMajorityOfBody, XcmPassthrough}; use polkadot_parachain_primitives::primitives::Sibling; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, - EnsureXcmOrigin, FixedWeightBounds, IsConcrete, NativeAsset, ParentAsSuperuser, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - UsingComponents, + EnsureXcmOrigin, FixedWeightBounds, FungibleAdapter, IsConcrete, NativeAsset, + ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, + SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, UsingComponents, }; use xcm_executor::XcmExecutor; @@ -109,7 +107,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("test-parachain"), impl_name: create_runtime_str!("test-parachain"), authoring_version: 1, - spec_version: 1_006_000, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, @@ -350,8 +348,7 @@ pub type LocationToAccountId = ( ); /// Means for transacting assets on this chain. -#[allow(deprecated)] -pub type CurrencyTransactor = CurrencyAdapter< +pub type CurrencyTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index cc0bd47cc77337362926c712b26ab7f6ae3d407d..84a232e954fc74a3dd4aef36750ef311794e72e5 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -16,13 +16,13 @@ path = "src/main.rs" [dependencies] async-trait = "0.1.74" -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.28" hex-literal = "0.4.1" -log = "0.4.20" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +log = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } # Local rococo-parachain-runtime = { path = "../parachains/runtimes/testing/rococo-parachain" } @@ -38,7 +38,7 @@ coretime-rococo-runtime = { path = "../parachains/runtimes/coretime/coretime-roc coretime-westend-runtime = { path = "../parachains/runtimes/coretime/coretime-westend" } bridge-hub-westend-runtime = { path = "../parachains/runtimes/bridge-hubs/bridge-hub-westend" } penpal-runtime = { path = "../parachains/runtimes/testing/penpal" } -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } people-rococo-runtime = { path = "../parachains/runtimes/people/people-rococo" } people-westend-runtime = { path = "../parachains/runtimes/people/people-westend" } parachains-common = { path = "../parachains/common" } diff --git a/cumulus/polkadot-parachain/chain-specs/coretime-rococo.json b/cumulus/polkadot-parachain/chain-specs/coretime-rococo.json new file mode 120000 index 0000000000000000000000000000000000000000..6e47a6ad08cc787e2a66f6426b1516769779e995 --- /dev/null +++ b/cumulus/polkadot-parachain/chain-specs/coretime-rococo.json @@ -0,0 +1 @@ +../../parachains/chain-specs/coretime-rococo.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/chain-specs/coretime-westend.json b/cumulus/polkadot-parachain/chain-specs/coretime-westend.json new file mode 120000 index 0000000000000000000000000000000000000000..80dd771db54fc6067f19fadd883dd2274fb21f9d --- /dev/null +++ b/cumulus/polkadot-parachain/chain-specs/coretime-westend.json @@ -0,0 +1 @@ +../../parachains/chain-specs/coretime-westend.json \ No newline at end of file diff --git a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs index 1db826ea7daf82e93c0099807bab7f44642084b3..15e8a1bf11a055e6c5b4950ad07d78cad72f81a8 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs @@ -25,7 +25,10 @@ use std::str::FromStr; #[derive(Debug, PartialEq)] pub enum BridgeHubRuntimeType { Kusama, + KusamaLocal, + Polkadot, + PolkadotLocal, Rococo, RococoLocal, @@ -44,7 +47,9 @@ impl FromStr for BridgeHubRuntimeType { fn from_str(value: &str) -> Result { match value { polkadot::BRIDGE_HUB_POLKADOT => Ok(BridgeHubRuntimeType::Polkadot), + polkadot::BRIDGE_HUB_POLKADOT_LOCAL => Ok(BridgeHubRuntimeType::PolkadotLocal), kusama::BRIDGE_HUB_KUSAMA => Ok(BridgeHubRuntimeType::Kusama), + kusama::BRIDGE_HUB_KUSAMA_LOCAL => Ok(BridgeHubRuntimeType::KusamaLocal), westend::BRIDGE_HUB_WESTEND => Ok(BridgeHubRuntimeType::Westend), westend::BRIDGE_HUB_WESTEND_LOCAL => Ok(BridgeHubRuntimeType::WestendLocal), westend::BRIDGE_HUB_WESTEND_DEVELOPMENT => Ok(BridgeHubRuntimeType::WestendDevelopment), @@ -103,6 +108,7 @@ impl BridgeHubRuntimeType { Some("Bob".to_string()), |_| (), ))), + other => Err(std::format!("No default config present for {:?}", other)), } } } @@ -242,6 +248,7 @@ pub mod rococo { /// Sub-module for Kusama setup pub mod kusama { pub(crate) const BRIDGE_HUB_KUSAMA: &str = "bridge-hub-kusama"; + pub(crate) const BRIDGE_HUB_KUSAMA_LOCAL: &str = "bridge-hub-kusama-local"; } /// Sub-module for Westend setup. @@ -358,4 +365,5 @@ pub mod westend { /// Sub-module for Polkadot setup pub mod polkadot { pub(crate) const BRIDGE_HUB_POLKADOT: &str = "bridge-hub-polkadot"; + pub(crate) const BRIDGE_HUB_POLKADOT_LOCAL: &str = "bridge-hub-polkadot-local"; } diff --git a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs index f7c362cbd457639e3409eb1f24d3358186914269..42d56fc80eb3eee2810da1a414a13e346d6f8d17 100644 --- a/cumulus/polkadot-parachain/src/chain_spec/coretime.rs +++ b/cumulus/polkadot-parachain/src/chain_spec/coretime.rs @@ -29,6 +29,8 @@ pub enum CoretimeRuntimeType { // Benchmarks RococoDevelopment, + // Live + Westend, // Local WestendLocal, // Benchmarks @@ -43,6 +45,7 @@ impl FromStr for CoretimeRuntimeType { rococo::CORETIME_ROCOCO => Ok(CoretimeRuntimeType::Rococo), rococo::CORETIME_ROCOCO_LOCAL => Ok(CoretimeRuntimeType::RococoLocal), rococo::CORETIME_ROCOCO_DEVELOPMENT => Ok(CoretimeRuntimeType::RococoDevelopment), + westend::CORETIME_WESTEND => Ok(CoretimeRuntimeType::Westend), westend::CORETIME_WESTEND_LOCAL => Ok(CoretimeRuntimeType::WestendLocal), westend::CORETIME_WESTEND_DEVELOPMENT => Ok(CoretimeRuntimeType::WestendDevelopment), _ => Err(format!("Value '{}' is not configured yet", value)), @@ -56,6 +59,7 @@ impl From for &str { CoretimeRuntimeType::Rococo => rococo::CORETIME_ROCOCO, CoretimeRuntimeType::RococoLocal => rococo::CORETIME_ROCOCO_LOCAL, CoretimeRuntimeType::RococoDevelopment => rococo::CORETIME_ROCOCO_DEVELOPMENT, + CoretimeRuntimeType::Westend => westend::CORETIME_WESTEND, CoretimeRuntimeType::WestendLocal => westend::CORETIME_WESTEND_LOCAL, CoretimeRuntimeType::WestendDevelopment => westend::CORETIME_WESTEND_DEVELOPMENT, } @@ -65,7 +69,7 @@ impl From for &str { impl From for ChainType { fn from(runtime_type: CoretimeRuntimeType) -> Self { match runtime_type { - CoretimeRuntimeType::Rococo => ChainType::Live, + CoretimeRuntimeType::Rococo | CoretimeRuntimeType::Westend => ChainType::Live, CoretimeRuntimeType::RococoLocal | CoretimeRuntimeType::WestendLocal => ChainType::Local, CoretimeRuntimeType::RococoDevelopment | CoretimeRuntimeType::WestendDevelopment => @@ -82,12 +86,15 @@ impl CoretimeRuntimeType { pub fn load_config(&self) -> Result, String> { match self { CoretimeRuntimeType::Rococo => Ok(Box::new(GenericChainSpec::from_json_bytes( - &include_bytes!("../../../parachains/chain-specs/coretime-rococo.json")[..], + &include_bytes!("../../chain-specs/coretime-rococo.json")[..], )?)), CoretimeRuntimeType::RococoLocal => Ok(Box::new(rococo::local_config(*self, "rococo-local"))), CoretimeRuntimeType::RococoDevelopment => Ok(Box::new(rococo::local_config(*self, "rococo-dev"))), + CoretimeRuntimeType::Westend => Ok(Box::new(GenericChainSpec::from_json_bytes( + &include_bytes!("../../../parachains/chain-specs/coretime-westend.json")[..], + )?)), CoretimeRuntimeType::WestendLocal => Ok(Box::new(westend::local_config(*self, "westend-local"))), CoretimeRuntimeType::WestendDevelopment => @@ -213,6 +220,7 @@ pub mod westend { use parachains_common::{AccountId, AuraId, Balance}; use sp_core::sr25519; + pub(crate) const CORETIME_WESTEND: &str = "coretime-westend"; pub(crate) const CORETIME_WESTEND_LOCAL: &str = "coretime-westend-local"; pub(crate) const CORETIME_WESTEND_DEVELOPMENT: &str = "coretime-westend-dev"; const CORETIME_WESTEND_ED: Balance = coretime_westend_runtime::ExistentialDeposit::get(); diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index a7319b6dfbb25b7d2c9ac3f2125ff03fe6f5be76..4d44879af51511e4f25b440fe92260e5fc066170 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -23,6 +23,7 @@ use crate::{ }, service::{new_partial, Block}, }; +use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; @@ -584,7 +585,7 @@ pub fn run() -> Result<()> { match cmd { BenchmarkCmd::Pallet(cmd) => if cfg!(feature = "runtime-benchmarks") { - runner.sync_run(|config| cmd.run::(config)) + runner.sync_run(|config| cmd.run::, ReclaimHostFunctions>(config)) } else { Err("Benchmarking wasn't enabled when building the node. \ You can enable it with `--features runtime-benchmarks`." @@ -695,7 +696,7 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), - AssetHubKusama | AssetHubWestend => + AssetHubKusama => crate::service::start_asset_hub_node::< RuntimeApi, AuraId, @@ -704,7 +705,7 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), - AssetHubRococo => + AssetHubRococo | AssetHubWestend => crate::service::start_asset_hub_lookahead_node::< RuntimeApi, AuraId, @@ -713,7 +714,7 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), - CollectivesPolkadot | CollectivesWestend => + CollectivesPolkadot => crate::service::start_generic_aura_node::< RuntimeApi, AuraId, @@ -722,6 +723,15 @@ pub fn run() -> Result<()> { .map(|r| r.0) .map_err(Into::into), + CollectivesWestend => + crate::service::start_generic_aura_lookahead_node::< + RuntimeApi, + AuraId, + >(config, polkadot_config, collator_options, id, hwbench) + .await + .map(|r| r.0) + .map_err(Into::into), + Seedling | Shell => crate::service::start_shell_node::( config, @@ -746,14 +756,16 @@ pub fn run() -> Result<()> { .map_err(Into::into), BridgeHub(bridge_hub_runtime_type) => match bridge_hub_runtime_type { - chain_spec::bridge_hubs::BridgeHubRuntimeType::Polkadot => + chain_spec::bridge_hubs::BridgeHubRuntimeType::Polkadot | + chain_spec::bridge_hubs::BridgeHubRuntimeType::PolkadotLocal => crate::service::start_generic_aura_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) .await .map(|r| r.0), - chain_spec::bridge_hubs::BridgeHubRuntimeType::Kusama => + chain_spec::bridge_hubs::BridgeHubRuntimeType::Kusama | + chain_spec::bridge_hubs::BridgeHubRuntimeType::KusamaLocal => crate::service::start_generic_aura_node::< RuntimeApi, AuraId, @@ -763,7 +775,7 @@ pub fn run() -> Result<()> { chain_spec::bridge_hubs::BridgeHubRuntimeType::Westend | chain_spec::bridge_hubs::BridgeHubRuntimeType::WestendLocal | chain_spec::bridge_hubs::BridgeHubRuntimeType::WestendDevelopment => - crate::service::start_generic_aura_node::< + crate::service::start_generic_aura_lookahead_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) @@ -772,7 +784,7 @@ pub fn run() -> Result<()> { chain_spec::bridge_hubs::BridgeHubRuntimeType::Rococo | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoLocal | chain_spec::bridge_hubs::BridgeHubRuntimeType::RococoDevelopment => - crate::service::start_generic_aura_node::< + crate::service::start_generic_aura_lookahead_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) @@ -785,9 +797,10 @@ pub fn run() -> Result<()> { chain_spec::coretime::CoretimeRuntimeType::Rococo | chain_spec::coretime::CoretimeRuntimeType::RococoLocal | chain_spec::coretime::CoretimeRuntimeType::RococoDevelopment | + chain_spec::coretime::CoretimeRuntimeType::Westend | chain_spec::coretime::CoretimeRuntimeType::WestendLocal | chain_spec::coretime::CoretimeRuntimeType::WestendDevelopment => - crate::service::start_generic_aura_node::< + crate::service::start_generic_aura_lookahead_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) @@ -824,7 +837,7 @@ pub fn run() -> Result<()> { chain_spec::people::PeopleRuntimeType::Westend | chain_spec::people::PeopleRuntimeType::WestendLocal | chain_spec::people::PeopleRuntimeType::WestendDevelopment => - crate::service::start_generic_aura_node::< + crate::service::start_generic_aura_lookahead_node::< RuntimeApi, AuraId, >(config, polkadot_config, collator_options, id, hwbench) diff --git a/cumulus/polkadot-parachain/src/service.rs b/cumulus/polkadot-parachain/src/service.rs index 61b9cbbd80d9fd2895d2c319f33d8c9fc8bc4b89..4e06cd38f1d7fc7f4c7bf861162a1c8cf5390ec2 100644 --- a/cumulus/polkadot-parachain/src/service.rs +++ b/cumulus/polkadot-parachain/src/service.rs @@ -68,11 +68,15 @@ use substrate_prometheus_endpoint::Registry; use polkadot_primitives::CollatorPair; #[cfg(not(feature = "runtime-benchmarks"))] -type HostFunctions = sp_io::SubstrateHostFunctions; +type HostFunctions = + (sp_io::SubstrateHostFunctions, cumulus_client_service::storage_proof_size::HostFunctions); #[cfg(feature = "runtime-benchmarks")] -type HostFunctions = - (sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions); +type HostFunctions = ( + sp_io::SubstrateHostFunctions, + cumulus_client_service::storage_proof_size::HostFunctions, + frame_benchmarking::benchmarking::HostFunctions, +); type ParachainClient = TFullClient>; @@ -274,10 +278,11 @@ where .build(); let (client, backend, keystore_container, task_manager) = - sc_service::new_full_parts::( + sc_service::new_full_parts_record_import::( config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, + true, )?; let client = Arc::new(client); @@ -939,8 +944,6 @@ pub async fn start_rococo_parachain_node( overseer_handle, announce_block, backend| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), client.clone(), @@ -971,7 +974,6 @@ pub async fn start_rococo_parachain_node( collator_key, para_id, overseer_handle, - slot_duration, relay_chain_slot_duration, proposer, collator_service, @@ -1292,7 +1294,7 @@ where Ok(BasicQueue::new(verifier, Box::new(block_import), None, &spawner, registry)) } -/// Start an aura powered parachain node. Collectives uses this. +/// Start an aura powered parachain node. Some system chains use this. pub async fn start_generic_aura_node( parachain_config: Configuration, polkadot_config: Configuration, @@ -1386,6 +1388,103 @@ where .await } +/// Uses the lookahead collator to support async backing. +/// +/// Start an aura powered parachain node. Some system chains use this. +pub async fn start_generic_aura_lookahead_node( + parachain_config: Configuration, + polkadot_config: Configuration, + collator_options: CollatorOptions, + para_id: ParaId, + hwbench: Option, +) -> sc_service::error::Result<(TaskManager, Arc>)> +where + RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, + RuntimeApi::RuntimeApi: sp_transaction_pool::runtime_api::TaggedTransactionQueue + + sp_api::Metadata + + sp_session::SessionKeys + + sp_api::ApiExt + + sp_offchain::OffchainWorkerApi + + sp_block_builder::BlockBuilder + + cumulus_primitives_core::CollectCollationInfo + + sp_consensus_aura::AuraApi::Pair as Pair>::Public> + + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + + frame_rpc_system::AccountNonceApi + + cumulus_primitives_aura::AuraUnincludedSegmentApi, + <::Pair as Pair>::Signature: + TryFrom> + std::hash::Hash + sp_runtime::traits::Member + Codec, +{ + start_node_impl::( + parachain_config, + polkadot_config, + collator_options, + CollatorSybilResistance::Resistant, // Aura + para_id, + |_| Ok(RpcModule::new(())), + aura_build_import_queue::<_, AuraId>, + |client, + block_import, + prometheus_registry, + telemetry, + task_manager, + relay_chain_interface, + transaction_pool, + sync_oracle, + keystore, + relay_chain_slot_duration, + para_id, + collator_key, + overseer_handle, + announce_block, + backend| { + let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( + task_manager.spawn_handle(), + client.clone(), + transaction_pool, + prometheus_registry, + telemetry.clone(), + ); + let proposer = Proposer::new(proposer_factory); + + let collator_service = CollatorService::new( + client.clone(), + Arc::new(task_manager.spawn_handle()), + announce_block, + client.clone(), + ); + + let params = AuraParams { + create_inherent_data_providers: move |_, ()| async move { Ok(()) }, + block_import, + para_client: client.clone(), + para_backend: backend, + relay_client: relay_chain_interface, + code_hash_provider: move |block_hash| { + client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) + }, + sync_oracle, + keystore, + collator_key, + para_id, + overseer_handle, + relay_chain_slot_duration, + proposer, + collator_service, + authoring_duration: Duration::from_millis(1500), + reinitialize: false, + }; + + let fut = + aura::run::::Pair, _, _, _, _, _, _, _, _, _>(params); + task_manager.spawn_essential_handle().spawn("aura", None, fut); + + Ok(()) + }, + hwbench, + ) + .await +} + /// Start a shell node which should later transition into an Aura powered parachain node. Asset Hub /// uses this because at genesis, Asset Hub was on the `shell` runtime which didn't have Aura and /// needs to sync and upgrade before it can run `AuraApi` functions. @@ -1637,14 +1736,6 @@ where } // Move to Aura consensus. - let slot_duration = match cumulus_client_consensus_aura::slot_duration(&*client) { - Ok(d) => d, - Err(e) => { - log::error!("Could not get Aura slot duration: {e}"); - return - }, - }; - let proposer = Proposer::new(proposer_factory); let params = AuraParams { @@ -1661,7 +1752,6 @@ where collator_key, para_id, overseer_handle, - slot_duration, relay_chain_slot_duration, proposer, collator_service, @@ -1732,8 +1822,6 @@ where overseer_handle, announce_block, backend| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), client.clone(), @@ -1764,7 +1852,6 @@ where collator_key, para_id, overseer_handle, - slot_duration, relay_chain_slot_duration, proposer, collator_service, @@ -1805,7 +1892,8 @@ where + sp_block_builder::BlockBuilder + cumulus_primitives_core::CollectCollationInfo + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi - + frame_rpc_system::AccountNonceApi, + + frame_rpc_system::AccountNonceApi + + cumulus_primitives_aura::AuraUnincludedSegmentApi, RB: Fn(Arc>) -> Result, sc_service::Error>, BIQ: FnOnce( Arc>, @@ -2039,9 +2127,7 @@ pub async fn start_contracts_rococo_node( collator_key, overseer_handle, announce_block, - _backend| { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - + backend| { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), client.clone(), @@ -2058,26 +2144,29 @@ pub async fn start_contracts_rococo_node( client.clone(), ); - let params = BasicAuraParams { + let params = AuraParams { create_inherent_data_providers: move |_, ()| async move { Ok(()) }, block_import, - para_client: client, + para_client: client.clone(), + para_backend: backend.clone(), relay_client: relay_chain_interface, + code_hash_provider: move |block_hash| { + client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) + }, sync_oracle, keystore, collator_key, para_id, overseer_handle, - slot_duration, relay_chain_slot_duration, proposer, collator_service, // Very limited proposal time. - authoring_duration: Duration::from_millis(500), - collation_request_receiver: None, + authoring_duration: Duration::from_millis(1500), + reinitialize: false, }; - let fut = basic_aura::run::< + let fut = aura::run::< Block, sp_consensus_aura::sr25519::AuthorityPair, _, @@ -2087,6 +2176,8 @@ pub async fn start_contracts_rococo_node( _, _, _, + _, + _, >(params); task_manager.spawn_essential_handle().spawn("aura", None, fut); diff --git a/cumulus/primitives/proof-size-hostfunction/src/lib.rs b/cumulus/primitives/proof-size-hostfunction/src/lib.rs index 6da6235e585a343887f87931e375b21bec48c20d..8ebc58ea450d4aea023f2a3af218bbd9eb3546e9 100644 --- a/cumulus/primitives/proof-size-hostfunction/src/lib.rs +++ b/cumulus/primitives/proof-size-hostfunction/src/lib.rs @@ -18,6 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "std")] use sp_externalities::ExternalitiesExt; use sp_runtime_interface::runtime_interface; @@ -35,7 +36,8 @@ pub const PROOF_RECORDING_DISABLED: u64 = u64::MAX; pub trait StorageProofSize { /// Returns the current storage proof size. fn storage_proof_size(&mut self) -> u64 { - self.extension::().map_or(u64::MAX, |e| e.storage_proof_size()) + self.extension::() + .map_or(PROOF_RECORDING_DISABLED, |e| e.storage_proof_size()) } } diff --git a/cumulus/primitives/storage-weight-reclaim/Cargo.toml b/cumulus/primitives/storage-weight-reclaim/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..4835fb5192b88165c1e7912862492086ddd0853e --- /dev/null +++ b/cumulus/primitives/storage-weight-reclaim/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "cumulus-primitives-storage-weight-reclaim" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +description = "Utilities to reclaim storage weight." +license = "Apache-2.0" + +[lints] +workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { workspace = true } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } + +frame-support = { path = "../../../substrate/frame/support", default-features = false } +frame-system = { path = "../../../substrate/frame/system", default-features = false } + +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-std = { path = "../../../substrate/primitives/std", default-features = false } + +cumulus-primitives-core = { path = "../../primitives/core", default-features = false } +cumulus-primitives-proof-size-hostfunction = { path = "../../primitives/proof-size-hostfunction", default-features = false } +docify = "0.2.7" + +[dev-dependencies] +sp-trie = { path = "../../../substrate/primitives/trie", default-features = false } +sp-io = { path = "../../../substrate/primitives/io", default-features = false } +cumulus-test-runtime = { path = "../../test/runtime" } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-primitives-core/std", + "cumulus-primitives-proof-size-hostfunction/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", +] diff --git a/cumulus/primitives/storage-weight-reclaim/src/lib.rs b/cumulus/primitives/storage-weight-reclaim/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..5dddc92e395574dbb36538f3799c0d21b22a3939 --- /dev/null +++ b/cumulus/primitives/storage-weight-reclaim/src/lib.rs @@ -0,0 +1,663 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Mechanism to reclaim PoV proof size weight after an extrinsic has been applied. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use cumulus_primitives_core::Weight; +use cumulus_primitives_proof_size_hostfunction::{ + storage_proof_size::storage_proof_size, PROOF_RECORDING_DISABLED, +}; +use frame_support::{ + dispatch::{DispatchInfo, PostDispatchInfo}, + weights::WeightMeter, +}; +use frame_system::Config; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension}, + transaction_validity::TransactionValidityError, + DispatchResult, +}; +use sp_std::marker::PhantomData; + +const LOG_TARGET: &'static str = "runtime::storage_reclaim"; + +/// `StorageWeightReclaimer` is a mechanism for manually reclaiming storage weight. +/// +/// It internally keeps track of the proof size and storage weight at initialization time. At +/// reclaim it computes the real consumed storage weight and refunds excess weight. +/// +/// # Example +#[doc = docify::embed!("src/lib.rs", simple_reclaimer_example)] +pub struct StorageWeightReclaimer { + previous_remaining_proof_size: u64, + previous_reported_proof_size: Option, +} + +impl StorageWeightReclaimer { + /// Creates a new `StorageWeightReclaimer` instance and initializes it with the storage + /// size provided by `weight_meter` and reported proof size from the node. + #[must_use = "Must call `reclaim_with_meter` to reclaim the weight"] + pub fn new(weight_meter: &WeightMeter) -> StorageWeightReclaimer { + let previous_remaining_proof_size = weight_meter.remaining().proof_size(); + let previous_reported_proof_size = get_proof_size(); + Self { previous_remaining_proof_size, previous_reported_proof_size } + } + + /// Check the consumed storage weight and calculate the consumed excess weight. + fn reclaim(&mut self, remaining_weight: Weight) -> Option { + let current_remaining_weight = remaining_weight.proof_size(); + let current_storage_proof_size = get_proof_size()?; + let previous_storage_proof_size = self.previous_reported_proof_size?; + let used_weight = + self.previous_remaining_proof_size.saturating_sub(current_remaining_weight); + let reported_used_size = + current_storage_proof_size.saturating_sub(previous_storage_proof_size); + let reclaimable = used_weight.saturating_sub(reported_used_size); + log::trace!( + target: LOG_TARGET, + "Found reclaimable storage weight. benchmarked: {used_weight}, consumed: {reported_used_size}" + ); + + self.previous_remaining_proof_size = current_remaining_weight.saturating_add(reclaimable); + self.previous_reported_proof_size = Some(current_storage_proof_size); + Some(Weight::from_parts(0, reclaimable)) + } + + /// Check the consumed storage weight and add the reclaimed + /// weight budget back to `weight_meter`. + pub fn reclaim_with_meter(&mut self, weight_meter: &mut WeightMeter) -> Option { + let reclaimed = self.reclaim(weight_meter.remaining())?; + weight_meter.reclaim_proof_size(reclaimed.proof_size()); + Some(reclaimed) + } +} + +/// Returns the current storage proof size from the host side. +/// +/// Returns `None` if proof recording is disabled on the host. +pub fn get_proof_size() -> Option { + let proof_size = storage_proof_size(); + (proof_size != PROOF_RECORDING_DISABLED).then_some(proof_size) +} + +/// Storage weight reclaim mechanism. +/// +/// This extension checks the size of the node-side storage proof +/// before and after executing a given extrinsic. The difference between +/// benchmarked and spent weight can be reclaimed. +#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct StorageWeightReclaim(PhantomData); + +impl StorageWeightReclaim { + /// Create a new `StorageWeightReclaim` instance. + pub fn new() -> Self { + Self(Default::default()) + } +} + +impl core::fmt::Debug for StorageWeightReclaim { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + let _ = write!(f, "StorageWeightReclaim"); + Ok(()) + } +} + +impl SignedExtension for StorageWeightReclaim +where + T::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "StorageWeightReclaim"; + + type AccountId = T::AccountId; + type Call = T::RuntimeCall; + type AdditionalSigned = (); + type Pre = Option; + + fn additional_signed( + &self, + ) -> Result + { + Ok(()) + } + + fn pre_dispatch( + self, + _who: &Self::AccountId, + _call: &Self::Call, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> Result { + Ok(get_proof_size()) + } + + fn post_dispatch( + pre: Option, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + let Some(Some(pre_dispatch_proof_size)) = pre else { + return Ok(()); + }; + + let Some(post_dispatch_proof_size) = get_proof_size() else { + log::debug!( + target: LOG_TARGET, + "Proof recording enabled during pre-dispatch, now disabled. This should not happen." + ); + return Ok(()) + }; + let benchmarked_weight = info.weight.proof_size(); + let consumed_weight = post_dispatch_proof_size.saturating_sub(pre_dispatch_proof_size); + + // Unspent weight according to the `actual_weight` from `PostDispatchInfo` + // This unspent weight will be refunded by the `CheckWeight` extension, so we need to + // account for that. + let unspent = post_info.calc_unspent(info).proof_size(); + let storage_size_diff = + benchmarked_weight.saturating_sub(unspent).abs_diff(consumed_weight as u64); + + // This value will be reclaimed by [`frame_system::CheckWeight`], so we need to calculate + // that in. + frame_system::BlockWeight::::mutate(|current| { + if consumed_weight > benchmarked_weight { + log::error!( + target: LOG_TARGET, + "Benchmarked storage weight smaller than consumed storage weight. benchmarked: {benchmarked_weight} consumed: {consumed_weight} unspent: {unspent}" + ); + current.accrue(Weight::from_parts(0, storage_size_diff), info.class) + } else { + log::trace!( + target: LOG_TARGET, + "Reclaiming storage weight. benchmarked: {benchmarked_weight}, consumed: {consumed_weight} unspent: {unspent}" + ); + current.reduce(Weight::from_parts(0, storage_size_diff), info.class) + } + }); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{ + assert_ok, + dispatch::DispatchClass, + weights::{Weight, WeightMeter}, + }; + use frame_system::{BlockWeight, CheckWeight}; + use sp_runtime::{AccountId32, BuildStorage}; + use sp_std::marker::PhantomData; + use sp_trie::proof_size_extension::ProofSizeExt; + + type Test = cumulus_test_runtime::Runtime; + const CALL: &::RuntimeCall = + &cumulus_test_runtime::RuntimeCall::System(frame_system::Call::set_heap_pages { + pages: 0u64, + }); + const ALICE: AccountId32 = AccountId32::new([1u8; 32]); + const LEN: usize = 0; + + pub fn new_test_ext() -> sp_io::TestExternalities { + let ext: sp_io::TestExternalities = cumulus_test_runtime::RuntimeGenesisConfig::default() + .build_storage() + .unwrap() + .into(); + ext + } + + struct TestRecorder { + return_values: Box<[usize]>, + counter: std::sync::atomic::AtomicUsize, + } + + impl TestRecorder { + fn new(values: &[usize]) -> Self { + TestRecorder { return_values: values.into(), counter: Default::default() } + } + } + + impl sp_trie::ProofSizeProvider for TestRecorder { + fn estimate_encoded_size(&self) -> usize { + let counter = self.counter.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + self.return_values[counter] + } + } + + fn setup_test_externalities(proof_values: &[usize]) -> sp_io::TestExternalities { + let mut test_ext = new_test_ext(); + let test_recorder = TestRecorder::new(proof_values); + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + test_ext + } + + fn set_current_storage_weight(new_weight: u64) { + BlockWeight::::mutate(|current_weight| { + current_weight.set(Weight::from_parts(0, new_weight), DispatchClass::Normal); + }); + } + + #[test] + fn basic_refund() { + // The real cost will be 100 bytes of storage size + let mut test_ext = setup_test_externalities(&[0, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(0)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + // We expect a refund of 400 + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 600); + }) + } + + #[test] + fn does_nothing_without_extension() { + let mut test_ext = new_test_ext(); + + // Proof size extension not registered + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, None); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1000); + }) + } + + #[test] + fn negative_refund_is_added_to_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 100 + let info = DispatchInfo { weight: Weight::from_parts(0, 100), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + // We expect no refund + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1100); + }) + } + + #[test] + fn test_zero_proof_size() { + let mut test_ext = setup_test_externalities(&[0, 0]); + + test_ext.execute_with(|| { + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(0)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 0); + }); + } + + #[test] + fn test_larger_pre_dispatch_proof_size() { + let mut test_ext = setup_test_externalities(&[300, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1300); + + let info = DispatchInfo { weight: Weight::from_parts(0, 500), ..Default::default() }; + let post_info = PostDispatchInfo::default(); + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(300)); + + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 800); + }); + } + + #[test] + fn test_incorporates_check_weight_unspent_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 300 + let info = DispatchInfo { weight: Weight::from_parts(100, 300), ..Default::default() }; + + // Actual weight is 50 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 250)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 900); + }) + } + + #[test] + fn test_incorporates_check_weight_unspent_weight_on_negative() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 50 + let info = DispatchInfo { weight: Weight::from_parts(100, 50), ..Default::default() }; + + // Actual weight is 25 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 25)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1150); + }) + } + + #[test] + fn test_incorporates_check_weight_unspent_weight_reverse_order() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 300 + let info = DispatchInfo { weight: Weight::from_parts(100, 300), ..Default::default() }; + + // Actual weight is 50 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 250)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + // `CheckWeight` gets called after `StorageWeightReclaim` this time. + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + + assert_eq!(BlockWeight::::get().total().proof_size(), 900); + }) + } + + #[test] + fn test_incorporates_check_weight_unspent_weight_on_negative_reverse_order() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 50 + let info = DispatchInfo { weight: Weight::from_parts(100, 50), ..Default::default() }; + + // Actual weight is 25 + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 25)), + pays_fee: Default::default(), + }; + + let pre = StorageWeightReclaim::(PhantomData) + .pre_dispatch(&ALICE, CALL, &info, LEN) + .unwrap(); + assert_eq!(pre, Some(100)); + + assert_ok!(StorageWeightReclaim::::post_dispatch( + Some(pre), + &info, + &post_info, + LEN, + &Ok(()) + )); + // `CheckWeight` gets called after `StorageWeightReclaim` this time. + // The `CheckWeight` extension will refunt `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(CheckWeight::::post_dispatch(None, &info, &post_info, 0, &Ok(()))); + + assert_eq!(BlockWeight::::get().total().proof_size(), 1150); + }) + } + + #[test] + fn storage_size_reported_correctly() { + let mut test_ext = setup_test_externalities(&[1000]); + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(1000)); + }); + + let mut test_ext = new_test_ext(); + + let test_recorder = TestRecorder::new(&[0]); + + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(0)); + }); + } + + #[test] + fn storage_size_disabled_reported_correctly() { + let mut test_ext = setup_test_externalities(&[PROOF_RECORDING_DISABLED as usize]); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), None); + }); + } + + #[test] + fn test_reclaim_helper() { + let mut test_ext = setup_test_externalities(&[1000, 1300, 1800]); + + test_ext.execute_with(|| { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(0, 2000)); + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + remaining_weight_meter.consume(Weight::from_parts(0, 500)); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + assert_eq!(reclaimed, Some(Weight::from_parts(0, 200))); + + remaining_weight_meter.consume(Weight::from_parts(0, 800)); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + assert_eq!(reclaimed, Some(Weight::from_parts(0, 300))); + assert_eq!(remaining_weight_meter.remaining(), Weight::from_parts(0, 1200)); + }); + } + + #[test] + fn test_reclaim_helper_does_not_reclaim_negative() { + // Benchmarked weight does not change at all + let mut test_ext = setup_test_externalities(&[1000, 1300]); + + test_ext.execute_with(|| { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(0, 1000)); + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + assert_eq!(reclaimed, Some(Weight::from_parts(0, 0))); + assert_eq!(remaining_weight_meter.remaining(), Weight::from_parts(0, 1000)); + }); + + // Benchmarked weight increases less than storage proof consumes + let mut test_ext = setup_test_externalities(&[1000, 1300]); + + test_ext.execute_with(|| { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(0, 1000)); + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + remaining_weight_meter.consume(Weight::from_parts(0, 0)); + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + assert_eq!(reclaimed, Some(Weight::from_parts(0, 0))); + }); + } + + /// Just here for doc purposes + fn get_benched_weight() -> Weight { + Weight::from_parts(0, 5) + } + + /// Just here for doc purposes + fn do_work() {} + + #[docify::export_content(simple_reclaimer_example)] + fn reclaim_with_weight_meter() { + let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(10, 10)); + + let benched_weight = get_benched_weight(); + + // It is important to instantiate the `StorageWeightReclaimer` before we consume the weight + // for a piece of work from the weight meter. + let mut reclaim_helper = StorageWeightReclaimer::new(&remaining_weight_meter); + + if remaining_weight_meter.try_consume(benched_weight).is_ok() { + // Perform some work that takes has `benched_weight` storage weight. + do_work(); + + // Reclaimer will detect that we only consumed 2 bytes, so 3 bytes are reclaimed. + let reclaimed = reclaim_helper.reclaim_with_meter(&mut remaining_weight_meter); + + // We reclaimed 3 bytes of storage size! + assert_eq!(reclaimed, Some(Weight::from_parts(0, 3))); + assert_eq!(BlockWeight::::get().total().proof_size(), 10); + assert_eq!(remaining_weight_meter.remaining(), Weight::from_parts(10, 8)); + } + } + + #[test] + fn test_reclaim_helper_works_with_meter() { + // The node will report 12 - 10 = 2 consumed storage size between the calls. + let mut test_ext = setup_test_externalities(&[10, 12]); + + test_ext.execute_with(|| { + // Initial storage size is 10. + set_current_storage_weight(10); + reclaim_with_weight_meter(); + }); + } +} diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index 27e9fbe3c7eb1157c4dbd2f631d4d63c4088e570..1e2c300b9ba257d2c8fb998689ae45847099dd63 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } # Substrate frame-support = { path = "../../../substrate/frame/support", default-features = false } @@ -26,7 +26,6 @@ polkadot-runtime-parachains = { path = "../../../polkadot/runtime/parachains", d xcm = { package = "staging-xcm", path = "../../../polkadot/xcm", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../../polkadot/xcm/xcm-executor", default-features = false } xcm-builder = { package = "staging-xcm-builder", path = "../../../polkadot/xcm/xcm-builder", default-features = false } -pallet-xcm-benchmarks = { path = "../../../polkadot/xcm/pallet-xcm-benchmarks", default-features = false } # Cumulus cumulus-primitives-core = { path = "../core", default-features = false } @@ -39,7 +38,6 @@ std = [ "frame-support/std", "log/std", "pallet-asset-conversion/std", - "pallet-xcm-benchmarks/std", "polkadot-runtime-common/std", "polkadot-runtime-parachains/std", "sp-io/std", @@ -54,7 +52,6 @@ runtime-benchmarks = [ "cumulus-primitives-core/runtime-benchmarks", "frame-support/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", - "pallet-xcm-benchmarks/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/cumulus/primitives/utility/src/lib.rs b/cumulus/primitives/utility/src/lib.rs index 0d8921227429c5c1e0f62a4f3c3f93db4ad73bcb..abc391bdcb8ed6ac3682c761e6a0a8d2d0e7f14b 100644 --- a/cumulus/primitives/utility/src/lib.rs +++ b/cumulus/primitives/utility/src/lib.rs @@ -760,7 +760,7 @@ mod test_trader { } } -/// Implementation of `pallet_xcm_benchmarks::EnsureDelivery` which helps to ensure delivery to the +/// Implementation of `xcm_builder::EnsureDelivery` which helps to ensure delivery to the /// parent relay chain. Deposits existential deposit for origin (if needed). /// Deposits estimated fee to the origin account (if needed). /// Allows to trigger additional logic for specific `ParaId` (e.g. open HRMP channel) (if neeeded). @@ -774,17 +774,22 @@ impl< XcmConfig: xcm_executor::Config, ExistentialDeposit: Get>, PriceForDelivery: PriceForMessageDelivery, - > pallet_xcm_benchmarks::EnsureDelivery + > xcm_builder::EnsureDelivery for ToParentDeliveryHelper { fn ensure_successful_delivery( origin_ref: &Location, - _dest: &Location, + dest: &Location, fee_reason: xcm_executor::traits::FeeReason, ) -> (Option, Option) { use xcm::latest::{MAX_INSTRUCTIONS_TO_DECODE, MAX_ITEMS_IN_ASSETS}; use xcm_executor::{traits::FeeManager, FeesMode}; + // check if the destination is relay/parent + if dest.ne(&Location::parent()) { + return (None, None); + } + let mut fees_mode = None; if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) { // if not waived, we need to set up accounts for paying and receiving fees diff --git a/cumulus/scripts/create_coretime_westend_spec.sh b/cumulus/scripts/create_coretime_westend_spec.sh index 90996f4a74f47f9783a29ff2ce358920be810641..516dc06ac6660c0c1f9d565d88a9ae7d132ade9e 100755 --- a/cumulus/scripts/create_coretime_westend_spec.sh +++ b/cumulus/scripts/create_coretime_westend_spec.sh @@ -39,13 +39,7 @@ cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtimeGenesi | jq '.chainType = "Live"' \ | jq '.bootNodes = [ "/dns/westend-coretime-collator-0.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", - "/dns/westend-coretime-collator-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH", - "/dns/westend-coretime-collator-2.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWAys2hVpF7AN8hYGnu1T6XYFRGKeBFqD8q5LUcvWXRLg8", - "/dns/westend-coretime-collator-3.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWSGgmiRryoi7A3qAmeYWgmVeGQkk66PrhDjJ6ZPP555as", - "/dns/westend-coretime-connect-0.polkadot.io/tcp/443/wss/p2p/12D3KooWP93Dzk8T7GWxyWw9jhLcz8Pksokk3R9vL2eEH337bNkT", - "/dns/westend-coretime-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH", - "/dns/westend-coretime-connect-2.polkadot.io/tcp/443/wss/p2p/12D3KooWAys2hVpF7AN8hYGnu1T6XYFRGKeBFqD8q5LUcvWXRLg8", - "/dns/westend-coretime-connect-3.polkadot.io/tcp/443/wss/p2p/12D3KooWSGgmiRryoi7A3qAmeYWgmVeGQkk66PrhDjJ6ZPP555as", + "/dns/westend-coretime-collator-1.parity-testnet.parity.io/tcp/30333/p2p/12D3KooWMh2imeAzsZKGQgm2cv6Uoep3GBYtwGfujt1bs5YfVzkH" ]' \ | jq '.relay_chain = "westend"' \ | jq --argjson para_id $para_id '.para_id = $para_id' \ @@ -53,37 +47,21 @@ cat chain-spec-plain.json | jq --rawfile code rt-hex.txt '.genesis.runtimeGenesi | jq '.genesis.runtimeGenesis.patch.balances.balances = []' \ | jq '.genesis.runtimeGenesis.patch.collatorSelection.invulnerables = [ "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", - "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", - "5D52g9Mt9jQnZn6hwYhv649QYqGwhjygxkpb6rm3FYzYHEs3", - "5Egx2B41PYj8uvuhkNJeucA54h6Xmi7ZH9wqrZLwj3CuvQKA" + "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG" ]' \ | jq '.genesis.runtimeGenesis.patch.session.keys = [ [ "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi", { - "aura": "0xbc3ea120d2991b75447b0b53cd8623970a0f6d98fa2701036c74d94e6b79252c" + "aura": "5GKXTtB7RG3mLJ2kT4AkDXoxvKCFDVUdwyRmeMEbX3gBwcGi" } ], [ "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG", { - "aura": "0x4acc970c28713ec93bf925352d3023418fdf89933227e1e2fdae8481103dfe28" - } - ], - [ - "5D52g9Mt9jQnZn6hwYhv649QYqGwhjygxkpb6rm3FYzYHEs3", - "5D52g9Mt9jQnZn6hwYhv649QYqGwhjygxkpb6rm3FYzYHEs3", - { - "aura": "0x2c7b95155708c10616b6f1a77a84f3d92c9a0272609ed24dbb7e6bdb81b53e76" - } - ], - [ - "5Egx2B41PYj8uvuhkNJeucA54h6Xmi7ZH9wqrZLwj3CuvQKA", - "5Egx2B41PYj8uvuhkNJeucA54h6Xmi7ZH9wqrZLwj3CuvQKA", - { - "aura": "0x741cfb39ec61bc76824ccec62d61670a80a890e0e21d58817f84040d3ec54474" + "aura": "5DknBCD1h49nc8eqnm6XtHz3bMQm5hfMuGYcLenRfCmpnBJG" } ] ]' \ diff --git a/cumulus/test/client/Cargo.toml b/cumulus/test/client/Cargo.toml index 7190172101cb509f7dd7c19ad25dc6d4d54036e7..028733ce23554967ee46e5ae8f2adebf6da70eb6 100644 --- a/cumulus/test/client/Cargo.toml +++ b/cumulus/test/client/Cargo.toml @@ -41,6 +41,7 @@ cumulus-test-relay-sproof-builder = { path = "../relay-sproof-builder" } cumulus-primitives-core = { path = "../../primitives/core" } cumulus-primitives-proof-size-hostfunction = { path = "../../primitives/proof-size-hostfunction" } cumulus-primitives-parachain-inherent = { path = "../../primitives/parachain-inherent" } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim" } [features] runtime-benchmarks = [ diff --git a/cumulus/test/client/src/lib.rs b/cumulus/test/client/src/lib.rs index df63f683de6b4312a953bbbf9f03862eac7ce451..c46f4da7f6788cf53dfdd6d7be5c221d81562e70 100644 --- a/cumulus/test/client/src/lib.rs +++ b/cumulus/test/client/src/lib.rs @@ -151,6 +151,7 @@ pub fn generate_extrinsic_with_pair( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::::new(), ); let function = function.into(); @@ -158,7 +159,7 @@ pub fn generate_extrinsic_with_pair( let raw_payload = SignedPayload::from_raw( function.clone(), extra.clone(), - ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), + ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), ); let signature = raw_payload.using_encoded(|e| origin.sign(e)); @@ -203,13 +204,16 @@ pub fn validate_block( let mut ext_ext = ext.ext(); let heap_pages = HeapAllocStrategy::Static { extra_pages: 1024 }; - let executor = WasmExecutor::::builder() - .with_execution_method(WasmExecutionMethod::default()) - .with_max_runtime_instances(1) - .with_runtime_cache_size(2) - .with_onchain_heap_alloc_strategy(heap_pages) - .with_offchain_heap_alloc_strategy(heap_pages) - .build(); + let executor = WasmExecutor::<( + sp_io::SubstrateHostFunctions, + cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions, + )>::builder() + .with_execution_method(WasmExecutionMethod::default()) + .with_max_runtime_instances(1) + .with_runtime_cache_size(2) + .with_onchain_heap_alloc_strategy(heap_pages) + .with_offchain_heap_alloc_strategy(heap_pages) + .build(); executor .uncached_call( diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 5902a62512bed772318145ccdb954ff2dfef4c92..449a8b819bc074e0c891d99d6fa2f42480f56ce6 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -39,6 +39,7 @@ sp-version = { path = "../../../substrate/primitives/version", default-features # Cumulus cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } cumulus-primitives-core = { path = "../../primitives/core", default-features = false } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder", optional = true } @@ -49,6 +50,7 @@ std = [ "codec/std", "cumulus-pallet-parachain-system/std", "cumulus-primitives-core/std", + "cumulus-primitives-storage-weight-reclaim/std", "frame-executive/std", "frame-support/std", "frame-system-rpc-runtime-api/std", diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index 6068f895c83bf6adba5e2d7fa68b3aa2b8821204..5fb3141098449ffa26f7e7ca09f30adee610f9e4 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -331,6 +331,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index c17cdf35419a22b932b164d0e3a71cce2525ff61..27273f4e0a8d35cfa1a21f45497c61a9b417d718 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -14,13 +14,13 @@ path = "src/main.rs" [dependencies] async-trait = "0.1.74" -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } criterion = { version = "0.5.1", features = ["async_tokio"] } -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } rand = "0.8.5" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } tokio = { version = "1.32.0", features = ["macros"] } tracing = "0.1.37" url = "2.4.0" @@ -81,6 +81,7 @@ cumulus-relay-chain-minimal-node = { path = "../../client/relay-chain-minimal-no cumulus-client-pov-recovery = { path = "../../client/pov-recovery" } cumulus-test-relay-sproof-builder = { path = "../relay-sproof-builder" } cumulus-pallet-parachain-system = { path = "../../pallets/parachain-system", default-features = false, features = ["parameterized-consensus-hook"] } +cumulus-primitives-storage-weight-reclaim = { path = "../../primitives/storage-weight-reclaim" } pallet-timestamp = { path = "../../../substrate/frame/timestamp" } [dev-dependencies] diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index aa2c4af97dfddd83e447bbf92f3b133daa30ce75..3554a383f219e7465f5df875bdf6463b93119f3b 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -67,7 +67,7 @@ use sc_network::{ use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, NetworkConfiguration, - OffchainWorkerConfig, PruningMode, WasmExecutionMethod, + OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig, WasmExecutionMethod, }, BasePath, ChainSpec as ChainSpecService, Configuration, Error as ServiceError, PartialComponents, Role, RpcHandlers, TFullBackend, TFullClient, TaskManager, @@ -112,7 +112,7 @@ pub type AnnounceBlockFn = Arc>) + Send + Sync>; pub struct RuntimeExecutor; impl sc_executor::NativeExecutionDispatch for RuntimeExecutor { - type ExtendHostFunctions = (); + type ExtendHostFunctions = cumulus_client_service::storage_proof_size::HostFunctions; fn dispatch(method: &str, data: &[u8]) -> Option> { cumulus_test_runtime::api::dispatch(method, data) @@ -801,6 +801,8 @@ pub fn node_config( rpc_max_subs_per_conn: Default::default(), rpc_port: 9945, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -892,11 +894,12 @@ pub fn construct_extrinsic( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::::new(), ); let raw_payload = runtime::SignedPayload::from_raw( function.clone(), extra.clone(), - ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), + ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), ); let signature = raw_payload.using_encoded(|e| caller.sign(e)); runtime::UncheckedExtrinsic::new_signed( diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index fb37fd01765e1623b1e412262948e1baae14f8d0..6b45770a8e3df47cb083dba5a8a0eeed1759e338 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } paste = "1.0.14" -log = { version = "0.4.20", default-features = false } +log = { workspace = true } lazy_static = "1.4.0" impl-trait-for-tuples = "0.2.2" diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..4bfb73acda05880ef49570594d0769d1e5e4b147 --- /dev/null +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -0,0 +1,58 @@ +# this image is built on top of existing Zombienet image +ARG ZOMBIENET_IMAGE +# this image uses substrate-relay image built elsewhere +ARG SUBSTRATE_RELAY_IMAGE=docker.io/paritytech/substrate-relay:v2023-11-07-rococo-westend-initial-relayer + +# metadata +ARG VCS_REF +ARG BUILD_DATE +ARG IMAGE_NAME + +# we need `substrate-relay` binary, built elsewhere +FROM ${SUBSTRATE_RELAY_IMAGE} as relay-builder + +# the base image is the zombienet image - we are planning to run zombienet tests using native +# provider here +FROM ${ZOMBIENET_IMAGE} + +LABEL io.parity.image.authors="devops-team@parity.io" \ + io.parity.image.vendor="Parity Technologies" \ + io.parity.image.title="${IMAGE_NAME}" \ + io.parity.image.description="Bridges Zombienet tests." \ + io.parity.image.source="https://github.com/paritytech/polkadot-sdk/blob/${VCS_REF}/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile" \ + io.parity.image.revision="${VCS_REF}" \ + io.parity.image.created="${BUILD_DATE}" \ + io.parity.image.documentation="https://github.com/paritytech/polkadot-sdk/bridges/testing" + +# show backtraces +ENV RUST_BACKTRACE 1 +USER root + +# for native provider to work (TODO: fix in zn docker?) +RUN apt-get update && apt-get install -y procps sudo +RUN yarn global add @polkadot/api-cli + +# add polkadot binary to the docker image +COPY ./artifacts/polkadot /usr/local/bin/ +COPY ./artifacts/polkadot-execute-worker /usr/local/bin/ +COPY ./artifacts/polkadot-prepare-worker /usr/local/bin/ +# add polkadot-parachain binary to the docker image +COPY ./artifacts/polkadot-parachain /usr/local/bin +# copy substrate-relay to the docker image +COPY --from=relay-builder /home/user/substrate-relay /usr/local/bin/ +# we need bridges zombienet runner and tests +RUN mkdir -p /home/nonroot/bridges-polkadot-sdk +COPY ./artifacts/bridges-polkadot-sdk /home/nonroot/bridges-polkadot-sdk +# also prepare `generate_hex_encoded_call` for running +RUN set -eux; \ + cd /home/nonroot/bridges-polkadot-sdk/bridges/testing/framework/utils/generate_hex_encoded_call; \ + npm install + +# check if executable works in this container +USER nonroot +RUN /usr/local/bin/polkadot --version +RUN /usr/local/bin/polkadot-parachain --version +RUN /usr/local/bin/substrate-relay --version + +# https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:{PORT}#/explorer +EXPOSE 9942 9910 8943 9945 9010 8945 diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index c998a601486aa1ef15c2481180eb9db41a3af29b..05aced8751aee8286c78ca4f5baeb398a0ac83f6 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -22,7 +22,7 @@ pallet-examples = { path = "../../substrate/frame/examples" } pallet-default-config-example = { path = "../../substrate/frame/examples/default-config" } # How we build docs in rust-docs -simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", rev = "e48b187bcfd5cc75111acd9d241f1bd36604344b" } +simple-mermaid = "0.1.1" docify = "0.2.7" # Polkadot SDK deps, typically all should only be in scope such that we can link to their doc item. @@ -61,6 +61,9 @@ sp-core = { path = "../../substrate/primitives/core" } sp-keyring = { path = "../../substrate/primitives/keyring" } sp-runtime = { path = "../../substrate/primitives/runtime" } +# XCM +xcm = { package = "staging-xcm", path = "../../polkadot/xcm" } + [dev-dependencies] parity-scale-codec = "3.6.5" scale-info = "2.9.0" diff --git a/docs/sdk/src/polkadot_sdk/xcm.rs b/docs/sdk/src/polkadot_sdk/xcm.rs index fd4d7f62aa702f5808552936404995ad67efaed9..5dcdc9e1de076c4507cb64517360df73e6733dc7 100644 --- a/docs/sdk/src/polkadot_sdk/xcm.rs +++ b/docs/sdk/src/polkadot_sdk/xcm.rs @@ -1,5 +1,71 @@ //! # XCM //! -//! @KiChjang @franciscoaguirre -//! TODO: RFCs, xcm-spec, the future of the repo, minimal example perhaps, forward to where actual -//! docs are hosted. +//! XCM, or Cross-Consensus Messaging, is a **language** to communicate **intentions** between +//! **consensus systems**. +//! +//! ## Overview +//! +//! XCM is a standard, whose specification lives in the [xcm format repo](https://github.com/paritytech/xcm-format). +//! It's agnostic both in programming language and blockchain platform, which means it could be used +//! in Rust in Polkadot, or in Go or C++ in any other platform like Cosmos or Ethereum. +//! +//! It enables different consensus systems to communicate with each other in an expressive manner. +//! Consensus systems include blockchains, smart contracts, and any other state machine that +//! achieves consensus in some way. +//! +//! XCM is executed on a virtual machine called the XCVM. +//! Scripts can be written with the XCM language, which are often called XCMs, messages or XCM +//! programs. Each program is a series of instructions, which get executed one after the other by +//! the virtual machine. These instructions aim to encompass all major things users typically do in +//! consensus systems. There are instructions on asset transferring, teleporting, locking, among +//! others. New instructions are added and changes to the XCVM are made via the [RFC process](https://github.com/paritytech/xcm-format/blob/master/proposals/0032-process.md). +//! +//! ## In Polkadot SDK +//! +//! The Polkadot SDK allows for easily deploying sovereign blockchains from scratch, all very +//! customizable. Dealing with many heterogeneous blockchains can be cumbersome. +//! XCM allows all these blockchains to communicate with an agreed-upon language. +//! As long as an implementation of the XCVM is implemented, the same XCM program can be executed in +//! all blockchains and perform the same task. +//! +//! ## Implementation +//! +//! A ready-to-use Rust implementation lives in the [polkadot-sdk repo](https://github.com/paritytech/polkadot-sdk/tree/master/polkadot/xcm), +//! but will be moved to its own repo in the future. +//! +//! Its main components are: +//! - `src`: the definition of the basic types and instructions +//! - [`xcm-executor`](https://paritytech.github.io/polkadot-sdk/master/staging_xcm_executor/struct.XcmExecutor.html): +//! an implementation of the virtual machine to execute instructions +//! - `pallet-xcm`: A FRAME pallet for interacting with the executor +//! - `xcm-builder`: a collection of types to configure the executor +//! - `xcm-simulator`: a playground for trying out different XCM programs and executor +//! configurations +//! +//! ## Example +//! +//! To perform the very usual operation of transferring assets, the following XCM program can be +//! used: +#![doc = docify::embed!("src/polkadot_sdk/xcm.rs", example_transfer)] +//! +//! ## Get started +//! +//! To learn how it works and to get started, go to the [XCM docs](https://paritytech.github.io/xcm-docs/). + +#[cfg(test)] +mod tests { + use xcm::latest::prelude::*; + + #[docify::export] + #[test] + fn example_transfer() { + let _transfer_program = Xcm::<()>(vec![ + WithdrawAsset((Here, 100u128).into()), + BuyExecution { fees: (Here, 100u128).into(), weight_limit: Unlimited }, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { id: [0u8; 32].into(), network: None }.into(), + }, + ]); + } +} diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index 3186e31f39e80f28e792842b3809e524fdac2d03..b9232f95981bf66427ffc67cc98282efb8cf330b 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -19,9 +19,9 @@ crate-type = ["cdylib", "rlib"] [dependencies] cfg-if = "1.0" -clap = { version = "4.4.18", features = ["derive"], optional = true } -log = "0.4.17" -thiserror = "1.0.48" +clap = { version = "4.5.1", features = ["derive"], optional = true } +log = { workspace = true, default-features = true } +thiserror = { workspace = true } futures = "0.3.21" pyro = { package = "pyroscope", version = "0.5.3", optional = true } pyroscope_pprofrs = { version = "0.2", optional = true } @@ -42,6 +42,7 @@ sc-tracing = { path = "../../substrate/client/tracing", optional = true } sc-sysinfo = { path = "../../substrate/client/sysinfo" } sc-executor = { path = "../../substrate/client/executor" } sc-storage-monitor = { path = "../../substrate/client/storage-monitor" } +sp-runtime = { path = "../../substrate/primitives/runtime" } [build-dependencies] substrate-build-script-utils = { path = "../../substrate/utils/build-script-utils" } @@ -63,9 +64,14 @@ runtime-benchmarks = [ "polkadot-node-metrics/runtime-benchmarks", "sc-service?/runtime-benchmarks", "service/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] full-node = ["service/full-node"] -try-runtime = ["service/try-runtime", "try-runtime-cli/try-runtime"] +try-runtime = [ + "service/try-runtime", + "sp-runtime/try-runtime", + "try-runtime-cli/try-runtime", +] fast-runtime = ["service/fast-runtime"] pyroscope = ["pyro", "pyroscope_pprofrs"] diff --git a/polkadot/cli/src/command.rs b/polkadot/cli/src/command.rs index 7319f28a2e2aa0a952b9f5d4d956fc9fc2321923..f71891ecde34c0caea9f0b0372f2eb7a1d648e7a 100644 --- a/polkadot/cli/src/command.rs +++ b/polkadot/cli/src/command.rs @@ -451,7 +451,7 @@ pub fn run() -> Result<()> { if cfg!(feature = "runtime-benchmarks") { runner.sync_run(|config| { - cmd.run::(config) + cmd.run::, ()>(config) .map_err(|e| Error::SubstrateCli(e)) }) } else { diff --git a/polkadot/erasure-coding/Cargo.toml b/polkadot/erasure-coding/Cargo.toml index 8705c14c3a4d026aa6dda5cc5232e9fdf10baf82..677f15c4b9a1446c7828c78f92933f7811733ee8 100644 --- a/polkadot/erasure-coding/Cargo.toml +++ b/polkadot/erasure-coding/Cargo.toml @@ -16,7 +16,7 @@ novelpoly = { package = "reed-solomon-novelpoly", version = "2.0.0" } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "std"] } sp-core = { path = "../../substrate/primitives/core" } sp-trie = { path = "../../substrate/primitives/trie" } -thiserror = "1.0.48" +thiserror = { workspace = true } [dev-dependencies] criterion = { version = "0.4.0", default-features = false, features = ["cargo_bench_support"] } diff --git a/polkadot/node/collation-generation/Cargo.toml b/polkadot/node/collation-generation/Cargo.toml index 8c6644ad6da59f51c2eb0ebb583fed0cd211d821..8df0c2b1edae47cad98881e7243eb1dc68449e9e 100644 --- a/polkadot/node/collation-generation/Cargo.toml +++ b/polkadot/node/collation-generation/Cargo.toml @@ -19,7 +19,7 @@ polkadot-node-subsystem-util = { path = "../subsystem-util" } polkadot-primitives = { path = "../../primitives" } sp-core = { path = "../../../substrate/primitives/core" } sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compressed-blob" } -thiserror = "1.0.48" +thiserror = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["bit-vec", "derive"] } [dev-dependencies] diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index 5ab823f489454e8cc8f3b7fa3fd32119484277fa..f6d89dbc15280efc51e685595cb62969d3d206e7 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -20,7 +20,7 @@ merlin = "3.0" schnorrkel = "0.11.4" kvdb = "0.13.0" derive_more = "0.99.17" -thiserror = "1.0.48" +thiserror = { workspace = true } itertools = "0.10.5" polkadot-node-subsystem = { path = "../../subsystem" } @@ -51,5 +51,5 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } assert_matches = "1.4.0" kvdb-memorydb = "0.13.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" } -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" diff --git a/polkadot/node/core/approval-voting/src/criteria.rs b/polkadot/node/core/approval-voting/src/criteria.rs index 1af61e72d7affa81744d8fcdb48ed5b1e80ae4d6..1ebea2641b62198c291951448c8c552d9cfb34cf 100644 --- a/polkadot/node/core/approval-voting/src/criteria.rs +++ b/polkadot/node/core/approval-voting/src/criteria.rs @@ -55,11 +55,11 @@ pub struct OurAssignment { } impl OurAssignment { - pub(crate) fn cert(&self) -> &AssignmentCertV2 { + pub fn cert(&self) -> &AssignmentCertV2 { &self.cert } - pub(crate) fn tranche(&self) -> DelayTranche { + pub fn tranche(&self) -> DelayTranche { self.tranche } @@ -225,7 +225,7 @@ fn assigned_core_transcript(core_index: CoreIndex) -> Transcript { /// Information about the world assignments are being produced in. #[derive(Clone, Debug)] -pub(crate) struct Config { +pub struct Config { /// The assignment public keys for validators. assignment_keys: Vec, /// The groups of validators assigned to each core. @@ -321,7 +321,7 @@ impl AssignmentCriteria for RealAssignmentCriteria { /// different times. The idea is that most assignments are never triggered and fall by the wayside. /// /// This will not assign to anything the local validator was part of the backing group for. -pub(crate) fn compute_assignments( +pub fn compute_assignments( keystore: &LocalKeystore, relay_vrf_story: RelayVRFStory, config: &Config, diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 3161d6186a1e64192951b5357ff22ef78a9d284f..456ae319787b07740bb0817dcfbe260500eb9dd8 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -92,11 +92,11 @@ use time::{slot_number_to_tick, Clock, ClockExt, DelayedApprovalTimer, SystemClo mod approval_checking; pub mod approval_db; mod backend; -mod criteria; +pub mod criteria; mod import; mod ops; mod persisted_entries; -mod time; +pub mod time; use crate::{ approval_checking::{Check, TranchesToApproveResult}, @@ -159,6 +159,7 @@ pub struct ApprovalVotingSubsystem { db: Arc, mode: Mode, metrics: Metrics, + clock: Box, } #[derive(Clone)] @@ -444,6 +445,25 @@ impl ApprovalVotingSubsystem { keystore: Arc, sync_oracle: Box, metrics: Metrics, + ) -> Self { + ApprovalVotingSubsystem::with_config_and_clock( + config, + db, + keystore, + sync_oracle, + metrics, + Box::new(SystemClock {}), + ) + } + + /// Create a new approval voting subsystem with the given keystore, config, and database. + pub fn with_config_and_clock( + config: Config, + db: Arc, + keystore: Arc, + sync_oracle: Box, + metrics: Metrics, + clock: Box, ) -> Self { ApprovalVotingSubsystem { keystore, @@ -452,6 +472,7 @@ impl ApprovalVotingSubsystem { db_config: DatabaseConfig { col_approval_data: config.col_approval_data }, mode: Mode::Syncing(sync_oracle), metrics, + clock, } } @@ -493,15 +514,10 @@ fn db_sanity_check(db: Arc, config: DatabaseConfig) -> SubsystemRe impl ApprovalVotingSubsystem { fn start(self, ctx: Context) -> SpawnedSubsystem { let backend = DbBackend::new(self.db.clone(), self.db_config); - let future = run::( - ctx, - self, - Box::new(SystemClock), - Box::new(RealAssignmentCriteria), - backend, - ) - .map_err(|e| SubsystemError::with_origin("approval-voting", e)) - .boxed(); + let future = + run::(ctx, self, Box::new(RealAssignmentCriteria), backend) + .map_err(|e| SubsystemError::with_origin("approval-voting", e)) + .boxed(); SpawnedSubsystem { name: "approval-voting-subsystem", future } } @@ -909,7 +925,6 @@ enum Action { async fn run( mut ctx: Context, mut subsystem: ApprovalVotingSubsystem, - clock: Box, assignment_criteria: Box, mut backend: B, ) -> SubsystemResult<()> @@ -923,7 +938,7 @@ where let mut state = State { keystore: subsystem.keystore, slot_duration_millis: subsystem.slot_duration_millis, - clock, + clock: subsystem.clock, assignment_criteria, spans: HashMap::new(), }; diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index 7a0bde6a55e28135036dbc9b8ee8137a0485aa28..9220e84a2554a48c14468567e87e74319ce94151 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -549,7 +549,7 @@ fn test_harness>( let subsystem = run( context, - ApprovalVotingSubsystem::with_config( + ApprovalVotingSubsystem::with_config_and_clock( Config { col_approval_data: test_constants::TEST_CONFIG.col_approval_data, slot_duration_millis: SLOT_DURATION_MILLIS, @@ -558,8 +558,8 @@ fn test_harness>( Arc::new(keystore), sync_oracle, Metrics::default(), + clock.clone(), ), - clock.clone(), assignment_criteria, backend, ); diff --git a/polkadot/node/core/approval-voting/src/time.rs b/polkadot/node/core/approval-voting/src/time.rs index 61091f3c34cdab4aed00c24bcbc8a40d6a77a116..99dfbe07678f2f0956565e92ffaf196b6f482af9 100644 --- a/polkadot/node/core/approval-voting/src/time.rs +++ b/polkadot/node/core/approval-voting/src/time.rs @@ -33,14 +33,14 @@ use std::{ }; use polkadot_primitives::{Hash, ValidatorIndex}; -const TICK_DURATION_MILLIS: u64 = 500; +pub const TICK_DURATION_MILLIS: u64 = 500; /// A base unit of time, starting from the Unix epoch, split into half-second intervals. -pub(crate) type Tick = u64; +pub type Tick = u64; /// A clock which allows querying of the current tick as well as /// waiting for a tick to be reached. -pub(crate) trait Clock { +pub trait Clock { /// Yields the current tick. fn tick_now(&self) -> Tick; @@ -49,7 +49,7 @@ pub(crate) trait Clock { } /// Extension methods for clocks. -pub(crate) trait ClockExt { +pub trait ClockExt { fn tranche_now(&self, slot_duration_millis: u64, base_slot: Slot) -> DelayTranche; } @@ -61,7 +61,8 @@ impl ClockExt for C { } /// A clock which uses the actual underlying system clock. -pub(crate) struct SystemClock; +#[derive(Clone)] +pub struct SystemClock; impl Clock for SystemClock { /// Yields the current tick. @@ -93,11 +94,22 @@ fn tick_to_time(tick: Tick) -> SystemTime { } /// assumes `slot_duration_millis` evenly divided by tick duration. -pub(crate) fn slot_number_to_tick(slot_duration_millis: u64, slot: Slot) -> Tick { +pub fn slot_number_to_tick(slot_duration_millis: u64, slot: Slot) -> Tick { let ticks_per_slot = slot_duration_millis / TICK_DURATION_MILLIS; u64::from(slot) * ticks_per_slot } +/// Converts a tick to the slot number. +pub fn tick_to_slot_number(slot_duration_millis: u64, tick: Tick) -> Slot { + let ticks_per_slot = slot_duration_millis / TICK_DURATION_MILLIS; + (tick / ticks_per_slot).into() +} + +/// Converts a tranche from a slot to the tick number. +pub fn tranche_to_tick(slot_duration_millis: u64, slot: Slot, tranche: u32) -> Tick { + slot_number_to_tick(slot_duration_millis, slot) + tranche as u64 +} + /// A list of delayed futures that gets triggered when the waiting time has expired and it is /// time to sign the candidate. /// We have a timer per relay-chain block. diff --git a/polkadot/node/core/av-store/Cargo.toml b/polkadot/node/core/av-store/Cargo.toml index 68769fb8cc8a66b94d52251d1e203555e900f43f..05212da7479848830a8fb70ee4c38d449e9a6a7e 100644 --- a/polkadot/node/core/av-store/Cargo.toml +++ b/polkadot/node/core/av-store/Cargo.toml @@ -13,7 +13,7 @@ workspace = true futures = "0.3.21" futures-timer = "3.0.2" kvdb = "0.13.0" -thiserror = "1.0.48" +thiserror = { workspace = true } gum = { package = "tracing-gum", path = "../../gum" } bitvec = "1.0.0" @@ -28,7 +28,7 @@ sp-consensus = { path = "../../../../substrate/primitives/consensus/common", def polkadot-node-jaeger = { path = "../../jaeger" } [dev-dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" assert_matches = "1.4.0" kvdb-memorydb = "0.13.0" diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index 1c69fc441b3284d1790e03f6896b67e71f32aae3..d0c1f9aa4832dfe629e1fb20d3082f27c260a7d5 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -20,8 +20,9 @@ erasure-coding = { package = "polkadot-erasure-coding", path = "../../../erasure statement-table = { package = "polkadot-statement-table", path = "../../../statement-table" } bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } gum = { package = "tracing-gum", path = "../../gum" } -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" +schnellru = "0.2.1" [dev-dependencies] sp-core = { path = "../../../../substrate/primitives/core" } @@ -31,5 +32,6 @@ sc-keystore = { path = "../../../../substrate/client/keystore" } sp-tracing = { path = "../../../../substrate/primitives/tracing" } futures = { version = "0.3.21", features = ["thread-pool"] } assert_matches = "1.4.0" +rstest = "0.18.2" polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../../primitives/test-helpers" } diff --git a/polkadot/node/core/backing/src/error.rs b/polkadot/node/core/backing/src/error.rs index 1b00a62510b7c634b3135a56ca6412c0948ef6a0..64955a393962076bd7b202dd31a7d69d46a4d1b9 100644 --- a/polkadot/node/core/backing/src/error.rs +++ b/polkadot/node/core/backing/src/error.rs @@ -48,6 +48,9 @@ pub enum Error { #[error("Candidate is not found")] CandidateNotFound, + #[error("CoreIndex cannot be determined for a candidate")] + CoreIndexUnavailable, + #[error("Signature is invalid")] InvalidSignature, diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index 98bbd6232add4d893293fc948c0a99bba8d46bf3..69bf2e956a0f6bb7f3b9f4a19446e5e7d2c125bb 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -77,6 +77,7 @@ use futures::{ stream::FuturesOrdered, FutureExt, SinkExt, StreamExt, TryFutureExt, }; +use schnellru::{ByLength, LruMap}; use error::{Error, FatalResult}; use polkadot_node_primitives::{ @@ -104,10 +105,12 @@ use polkadot_node_subsystem_util::{ Validator, }; use polkadot_primitives::{ + vstaging::{node_features::FeatureIndex, NodeFeatures}, BackedCandidate, CandidateCommitments, CandidateHash, CandidateReceipt, - CommittedCandidateReceipt, CoreIndex, CoreState, ExecutorParams, Hash, Id as ParaId, - PersistedValidationData, PvfExecKind, SigningContext, ValidationCode, ValidatorId, - ValidatorIndex, ValidatorSignature, ValidityAttestation, + CommittedCandidateReceipt, CoreIndex, CoreState, ExecutorParams, GroupIndex, GroupRotationInfo, + Hash, Id as ParaId, IndexedVec, PersistedValidationData, PvfExecKind, SessionIndex, + SigningContext, ValidationCode, ValidatorId, ValidatorIndex, ValidatorSignature, + ValidityAttestation, }; use sp_keystore::KeystorePtr; use statement_table::{ @@ -118,7 +121,7 @@ use statement_table::{ }, Config as TableConfig, Context as TableContextTrait, Table, }; -use util::vstaging::get_disabled_validators_with_fallback; +use util::{runtime::request_node_features, vstaging::get_disabled_validators_with_fallback}; mod error; @@ -209,7 +212,9 @@ struct PerRelayParentState { /// The hash of the relay parent on top of which this job is doing it's work. parent: Hash, /// The `ParaId` assigned to the local validator at this relay parent. - assignment: Option, + assigned_para: Option, + /// The `CoreIndex` assigned to the local validator at this relay parent. + assigned_core: Option, /// The candidates that are backed by enough validators in their group, by hash. backed: HashSet, /// The table of candidates and statements under this relay-parent. @@ -224,6 +229,15 @@ struct PerRelayParentState { fallbacks: HashMap, /// The minimum backing votes threshold. minimum_backing_votes: u32, + /// If true, we're appending extra bits in the BackedCandidate validator indices bitfield, + /// which represent the assigned core index. True if ElasticScalingMVP is enabled. + inject_core_index: bool, + /// The core states for all cores. + cores: Vec, + /// The validator index -> group mapping at this relay parent. + validator_to_group: Arc>>, + /// The associated group rotation information. + group_rotation_info: GroupRotationInfo, } struct PerCandidateState { @@ -275,6 +289,9 @@ struct State { /// This is guaranteed to have an entry for each candidate with a relay parent in the implicit /// or explicit view for which a `Seconded` statement has been successfully imported. per_candidate: HashMap, + /// Cache the per-session Validator->Group mapping. + validator_to_group_cache: + LruMap>>>, /// A cloneable sender which is dispatched to background candidate validation tasks to inform /// the main task of the result. background_validation_tx: mpsc::Sender<(Hash, ValidatedCandidateCommand)>, @@ -292,6 +309,7 @@ impl State { per_leaf: HashMap::default(), per_relay_parent: HashMap::default(), per_candidate: HashMap::new(), + validator_to_group_cache: LruMap::new(ByLength::new(2)), background_validation_tx, keystore, } @@ -379,10 +397,10 @@ struct AttestingData { backing: Vec, } -#[derive(Default)] +#[derive(Default, Debug)] struct TableContext { validator: Option, - groups: HashMap>, + groups: HashMap>, validators: Vec, disabled_validators: Vec, } @@ -404,7 +422,7 @@ impl TableContext { impl TableContextTrait for TableContext { type AuthorityId = ValidatorIndex; type Digest = CandidateHash; - type GroupId = ParaId; + type GroupId = CoreIndex; type Signature = ValidatorSignature; type Candidate = CommittedCandidateReceipt; @@ -412,15 +430,11 @@ impl TableContextTrait for TableContext { candidate.hash() } - fn candidate_group(candidate: &CommittedCandidateReceipt) -> ParaId { - candidate.descriptor().para_id + fn is_member_of(&self, authority: &ValidatorIndex, core: &CoreIndex) -> bool { + self.groups.get(core).map_or(false, |g| g.iter().any(|a| a == authority)) } - fn is_member_of(&self, authority: &ValidatorIndex, group: &ParaId) -> bool { - self.groups.get(group).map_or(false, |g| g.iter().any(|a| a == authority)) - } - - fn get_group_size(&self, group: &ParaId) -> Option { + fn get_group_size(&self, group: &CoreIndex) -> Option { self.groups.get(group).map(|g| g.len()) } } @@ -442,19 +456,20 @@ fn primitive_statement_to_table(s: &SignedFullStatementWithPVD) -> TableSignedSt fn table_attested_to_backed( attested: TableAttestedCandidate< - ParaId, + CoreIndex, CommittedCandidateReceipt, ValidatorIndex, ValidatorSignature, >, table_context: &TableContext, + inject_core_index: bool, ) -> Option { - let TableAttestedCandidate { candidate, validity_votes, group_id: para_id } = attested; + let TableAttestedCandidate { candidate, validity_votes, group_id: core_index } = attested; let (ids, validity_votes): (Vec<_>, Vec) = validity_votes.into_iter().map(|(id, vote)| (id, vote.into())).unzip(); - let group = table_context.groups.get(¶_id)?; + let group = table_context.groups.get(&core_index)?; let mut validator_indices = BitVec::with_capacity(group.len()); @@ -479,14 +494,15 @@ fn table_attested_to_backed( } vote_positions.sort_by_key(|(_orig, pos_in_group)| *pos_in_group); - Some(BackedCandidate { + Some(BackedCandidate::new( candidate, - validity_votes: vote_positions + vote_positions .into_iter() .map(|(pos_in_votes, _pos_in_group)| validity_votes[pos_in_votes].clone()) .collect(), validator_indices, - }) + inject_core_index.then_some(core_index), + )) } async fn store_available_data( @@ -971,7 +987,14 @@ async fn handle_active_leaves_update( // construct a `PerRelayParent` from the runtime API // and insert it. - let per = construct_per_relay_parent_state(ctx, maybe_new, &state.keystore, mode).await?; + let per = construct_per_relay_parent_state( + ctx, + maybe_new, + &state.keystore, + &mut state.validator_to_group_cache, + mode, + ) + .await?; if let Some(per) = per { state.per_relay_parent.insert(maybe_new, per); @@ -981,31 +1004,112 @@ async fn handle_active_leaves_update( Ok(()) } +macro_rules! try_runtime_api { + ($x: expr) => { + match $x { + Ok(x) => x, + Err(err) => { + // Only bubble up fatal errors. + error::log_error(Err(Into::::into(err).into()))?; + + // We can't do candidate validation work if we don't have the + // requisite runtime API data. But these errors should not take + // down the node. + return Ok(None) + }, + } + }; +} + +fn core_index_from_statement( + validator_to_group: &IndexedVec>, + group_rotation_info: &GroupRotationInfo, + cores: &[CoreState], + statement: &SignedFullStatementWithPVD, +) -> Option { + let compact_statement = statement.as_unchecked(); + let candidate_hash = CandidateHash(*compact_statement.unchecked_payload().candidate_hash()); + + let n_cores = cores.len(); + + gum::trace!( + target:LOG_TARGET, + ?group_rotation_info, + ?statement, + ?validator_to_group, + n_cores = ?cores.len(), + ?candidate_hash, + "Extracting core index from statement" + ); + + let statement_validator_index = statement.validator_index(); + let Some(Some(group_index)) = validator_to_group.get(statement_validator_index) else { + gum::debug!( + target: LOG_TARGET, + ?group_rotation_info, + ?statement, + ?validator_to_group, + n_cores = ?cores.len() , + ?candidate_hash, + "Invalid validator index: {:?}", + statement_validator_index + ); + return None + }; + + // First check if the statement para id matches the core assignment. + let core_index = group_rotation_info.core_for_group(*group_index, n_cores); + + if core_index.0 as usize > n_cores { + gum::warn!(target: LOG_TARGET, ?candidate_hash, ?core_index, n_cores, "Invalid CoreIndex"); + return None + } + + if let StatementWithPVD::Seconded(candidate, _pvd) = statement.payload() { + let candidate_para_id = candidate.descriptor.para_id; + let assigned_para_id = match &cores[core_index.0 as usize] { + CoreState::Free => { + gum::debug!(target: LOG_TARGET, ?candidate_hash, "Invalid CoreIndex, core is not assigned to any para_id"); + return None + }, + CoreState::Occupied(occupied) => + if let Some(next) = &occupied.next_up_on_available { + next.para_id + } else { + return None + }, + CoreState::Scheduled(scheduled) => scheduled.para_id, + }; + + if assigned_para_id != candidate_para_id { + gum::debug!( + target: LOG_TARGET, + ?candidate_hash, + ?core_index, + ?assigned_para_id, + ?candidate_para_id, + "Invalid CoreIndex, core is assigned to a different para_id" + ); + return None + } + return Some(core_index) + } else { + return Some(core_index) + } +} + /// Load the data necessary to do backing work on top of a relay-parent. #[overseer::contextbounds(CandidateBacking, prefix = self::overseer)] async fn construct_per_relay_parent_state( ctx: &mut Context, relay_parent: Hash, keystore: &KeystorePtr, + validator_to_group_cache: &mut LruMap< + SessionIndex, + Arc>>, + >, mode: ProspectiveParachainsMode, ) -> Result, Error> { - macro_rules! try_runtime_api { - ($x: expr) => { - match $x { - Ok(x) => x, - Err(err) => { - // Only bubble up fatal errors. - error::log_error(Err(Into::::into(err).into()))?; - - // We can't do candidate validation work if we don't have the - // requisite runtime API data. But these errors should not take - // down the node. - return Ok(None) - }, - } - }; - } - let parent = relay_parent; let (session_index, validators, groups, cores) = futures::try_join!( @@ -1020,6 +1124,16 @@ async fn construct_per_relay_parent_state( .map_err(Error::JoinMultiple)?; let session_index = try_runtime_api!(session_index); + + let inject_core_index = request_node_features(parent, session_index, ctx.sender()) + .await? + .unwrap_or(NodeFeatures::EMPTY) + .get(FeatureIndex::ElasticScalingMVP as usize) + .map(|b| *b) + .unwrap_or(false); + + gum::debug!(target: LOG_TARGET, inject_core_index, ?parent, "New state"); + let validators: Vec<_> = try_runtime_api!(validators); let (validator_groups, group_rotation_info) = try_runtime_api!(groups); let cores = try_runtime_api!(cores); @@ -1055,18 +1169,24 @@ async fn construct_per_relay_parent_state( }, }; - let mut groups = HashMap::new(); let n_cores = cores.len(); - let mut assignment = None; - for (idx, core) in cores.into_iter().enumerate() { + let mut groups = HashMap::>::new(); + let mut assigned_core = None; + let mut assigned_para = None; + + for (idx, core) in cores.iter().enumerate() { let core_para_id = match core { CoreState::Scheduled(scheduled) => scheduled.para_id, CoreState::Occupied(occupied) => if mode.is_enabled() { // Async backing makes it legal to build on top of // occupied core. - occupied.candidate_descriptor.para_id + if let Some(next) = &occupied.next_up_on_available { + next.para_id + } else { + continue + } } else { continue }, @@ -1077,11 +1197,27 @@ async fn construct_per_relay_parent_state( let group_index = group_rotation_info.group_for_core(core_index, n_cores); if let Some(g) = validator_groups.get(group_index.0 as usize) { if validator.as_ref().map_or(false, |v| g.contains(&v.index())) { - assignment = Some(core_para_id); + assigned_para = Some(core_para_id); + assigned_core = Some(core_index); } - groups.insert(core_para_id, g.clone()); + groups.insert(core_index, g.clone()); } } + gum::debug!(target: LOG_TARGET, ?groups, "TableContext"); + + let validator_to_group = validator_to_group_cache + .get_or_insert(session_index, || { + let mut vector = vec![None; validators.len()]; + + for (group_idx, validator_group) in validator_groups.iter().enumerate() { + for validator in validator_group { + vector[validator.0 as usize] = Some(GroupIndex(group_idx as u32)); + } + } + + Arc::new(IndexedVec::<_, _>::from(vector)) + }) + .expect("Just inserted"); let table_context = TableContext { validator, groups, validators, disabled_validators }; let table_config = TableConfig { @@ -1094,7 +1230,8 @@ async fn construct_per_relay_parent_state( Ok(Some(PerRelayParentState { prospective_parachains_mode: mode, parent, - assignment, + assigned_core, + assigned_para, backed: HashSet::new(), table: Table::new(table_config), table_context, @@ -1102,6 +1239,10 @@ async fn construct_per_relay_parent_state( awaiting_validation: HashSet::new(), fallbacks: HashMap::new(), minimum_backing_votes, + inject_core_index, + cores, + validator_to_group: validator_to_group.clone(), + group_rotation_info, })) } @@ -1519,15 +1660,16 @@ async fn import_statement( per_candidate: &mut HashMap, statement: &SignedFullStatementWithPVD, ) -> Result, Error> { + let candidate_hash = statement.payload().candidate_hash(); + gum::debug!( target: LOG_TARGET, statement = ?statement.payload().to_compact(), validator_index = statement.validator_index().0, + ?candidate_hash, "Importing statement", ); - let candidate_hash = statement.payload().candidate_hash(); - // If this is a new candidate (statement is 'seconded' and candidate is unknown), // we need to create an entry in the `PerCandidateState` map. // @@ -1593,7 +1735,15 @@ async fn import_statement( let stmt = primitive_statement_to_table(statement); - Ok(rp_state.table.import_statement(&rp_state.table_context, stmt)) + let core = core_index_from_statement( + &rp_state.validator_to_group, + &rp_state.group_rotation_info, + &rp_state.cores, + statement, + ) + .ok_or(Error::CoreIndexUnavailable)?; + + Ok(rp_state.table.import_statement(&rp_state.table_context, core, stmt)) } /// Handles a summary received from [`import_statement`] and dispatches `Backed` notifications and @@ -1615,8 +1765,12 @@ async fn post_import_statement_actions( // `HashSet::insert` returns true if the thing wasn't in there already. if rp_state.backed.insert(candidate_hash) { - if let Some(backed) = table_attested_to_backed(attested, &rp_state.table_context) { - let para_id = backed.candidate.descriptor.para_id; + if let Some(backed) = table_attested_to_backed( + attested, + &rp_state.table_context, + rp_state.inject_core_index, + ) { + let para_id = backed.candidate().descriptor.para_id; gum::debug!( target: LOG_TARGET, candidate_hash = ?candidate_hash, @@ -1637,7 +1791,7 @@ async fn post_import_statement_actions( // notify collator protocol. ctx.send_message(CollatorProtocolMessage::Backed { para_id, - para_head: backed.candidate.descriptor.para_head, + para_head: backed.candidate().descriptor.para_head, }) .await; // Notify statement distribution of backed candidate. @@ -1654,8 +1808,14 @@ async fn post_import_statement_actions( ); ctx.send_unbounded_message(message); } + } else { + gum::debug!(target: LOG_TARGET, ?candidate_hash, "Cannot get BackedCandidate"); } + } else { + gum::debug!(target: LOG_TARGET, ?candidate_hash, "Candidate already known"); } + } else { + gum::debug!(target: LOG_TARGET, "No attested candidate"); } issue_new_misbehaviors(ctx, rp_state.parent, &mut rp_state.table); @@ -1722,18 +1882,20 @@ async fn background_validate_and_make_available( if rp_state.awaiting_validation.insert(candidate_hash) { // spawn background task. let bg = async move { - if let Err(e) = validate_and_make_available(params).await { - if let Error::BackgroundValidationMpsc(error) = e { + if let Err(error) = validate_and_make_available(params).await { + if let Error::BackgroundValidationMpsc(error) = error { gum::debug!( target: LOG_TARGET, + ?candidate_hash, ?error, "Mpsc background validation mpsc died during validation- leaf no longer active?" ); } else { gum::error!( target: LOG_TARGET, - "Failed to validate and make available: {:?}", - e + ?candidate_hash, + ?error, + "Failed to validate and make available", ); } } @@ -1859,9 +2021,10 @@ async fn maybe_validate_and_import( let candidate_hash = summary.candidate; - if Some(summary.group_id) != rp_state.assignment { + if Some(summary.group_id) != rp_state.assigned_core { return Ok(()) } + let attesting = match statement.payload() { StatementWithPVD::Seconded(receipt, _) => { let attesting = AttestingData { @@ -2004,10 +2167,11 @@ async fn handle_second_message( } // Sanity check that candidate is from our assignment. - if Some(candidate.descriptor().para_id) != rp_state.assignment { + if Some(candidate.descriptor().para_id) != rp_state.assigned_para { gum::debug!( target: LOG_TARGET, - our_assignment = ?rp_state.assignment, + our_assignment_core = ?rp_state.assigned_core, + our_assignment_para = ?rp_state.assigned_para, collation = ?candidate.descriptor().para_id, "Subsystem asked to second for para outside of our assignment", ); @@ -2015,6 +2179,14 @@ async fn handle_second_message( return Ok(()) } + gum::debug!( + target: LOG_TARGET, + our_assignment_core = ?rp_state.assigned_core, + our_assignment_para = ?rp_state.assigned_para, + collation = ?candidate.descriptor().para_id, + "Current assignments vs collation", + ); + // If the message is a `CandidateBackingMessage::Second`, sign and dispatch a // Seconded statement only if we have not signed a Valid statement for the requested candidate. // @@ -2087,7 +2259,13 @@ fn handle_get_backed_candidates_message( &rp_state.table_context, rp_state.minimum_backing_votes, ) - .and_then(|attested| table_attested_to_backed(attested, &rp_state.table_context)) + .and_then(|attested| { + table_attested_to_backed( + attested, + &rp_state.table_context, + rp_state.inject_core_index, + ) + }) }) .collect(); diff --git a/polkadot/node/core/backing/src/tests/mod.rs b/polkadot/node/core/backing/src/tests/mod.rs index 1957f4e19c54bd9845e5bb5bd03383985d9c9636..e3cc5727435a3fef3532a9de342cd194ecd4fe3a 100644 --- a/polkadot/node/core/backing/src/tests/mod.rs +++ b/polkadot/node/core/backing/src/tests/mod.rs @@ -33,9 +33,10 @@ use polkadot_node_subsystem::{ }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ - CandidateDescriptor, GroupRotationInfo, HeadData, PersistedValidationData, PvfExecKind, - ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, + vstaging::node_features, CandidateDescriptor, GroupRotationInfo, HeadData, + PersistedValidationData, PvfExecKind, ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, }; +use rstest::rstest; use sp_application_crypto::AppCrypto; use sp_keyring::Sr25519Keyring; use sp_keystore::Keystore; @@ -65,19 +66,21 @@ fn dummy_pvd() -> PersistedValidationData { } } -struct TestState { +pub(crate) struct TestState { chain_ids: Vec, keystore: KeystorePtr, validators: Vec, validator_public: Vec, validation_data: PersistedValidationData, validator_groups: (Vec>, GroupRotationInfo), + validator_to_group: IndexedVec>, availability_cores: Vec, head_data: HashMap, signing_context: SigningContext, relay_parent: Hash, minimum_backing_votes: u32, disabled_validators: Vec, + node_features: NodeFeatures, } impl TestState { @@ -114,6 +117,11 @@ impl Default for TestState { .into_iter() .map(|g| g.into_iter().map(ValidatorIndex).collect()) .collect(); + let validator_to_group: IndexedVec<_, _> = + vec![Some(0), Some(1), Some(0), Some(0), None, Some(0)] + .into_iter() + .map(|x| x.map(|x| GroupIndex(x))) + .collect(); let group_rotation_info = GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 100, now: 1 }; @@ -143,6 +151,7 @@ impl Default for TestState { validators, validator_public, validator_groups: (validator_groups, group_rotation_info), + validator_to_group, availability_cores, head_data, validation_data, @@ -150,6 +159,7 @@ impl Default for TestState { relay_parent, minimum_backing_votes: LEGACY_MIN_BACKING_VOTES, disabled_validators: Vec::new(), + node_features: Default::default(), } } } @@ -285,6 +295,16 @@ async fn test_startup(virtual_overseer: &mut VirtualOverseer, test_state: &TestS } ); + // Node features request from runtime: all features are disabled. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_parent, RuntimeApiRequest::NodeFeatures(_session_index, tx)) + ) => { + tx.send(Ok(test_state.node_features.clone())).unwrap(); + } + ); + // Check if subsystem job issues a request for the minimum backing votes. assert_matches!( virtual_overseer.recv().await, @@ -477,9 +497,20 @@ fn backing_second_works() { } // Test that the candidate reaches quorum successfully. -#[test] -fn backing_works() { - let test_state = TestState::default(); +#[rstest] +#[case(true)] +#[case(false)] +fn backing_works(#[case] elastic_scaling_mvp: bool) { + let mut test_state = TestState::default(); + if elastic_scaling_mvp { + test_state + .node_features + .resize((node_features::FeatureIndex::ElasticScalingMVP as u8 + 1) as usize, false); + test_state + .node_features + .set(node_features::FeatureIndex::ElasticScalingMVP as u8 as usize, true); + } + test_harness(test_state.keystore.clone(), |mut virtual_overseer| async move { test_startup(&mut virtual_overseer, &test_state).await; @@ -630,6 +661,31 @@ fn backing_works() { virtual_overseer.send(FromOrchestra::Communication { msg: statement }).await; + let (tx, rx) = oneshot::channel(); + let msg = CandidateBackingMessage::GetBackedCandidates( + vec![(candidate_a_hash, test_state.relay_parent)], + tx, + ); + + virtual_overseer.send(FromOrchestra::Communication { msg }).await; + + let candidates = rx.await.unwrap(); + assert_eq!(1, candidates.len()); + assert_eq!(candidates[0].validity_votes().len(), 3); + + let (validator_indices, maybe_core_index) = + candidates[0].validator_indices_and_core_index(elastic_scaling_mvp); + if elastic_scaling_mvp { + assert_eq!(maybe_core_index.unwrap(), CoreIndex(0)); + } else { + assert!(maybe_core_index.is_none()); + } + + assert_eq!( + validator_indices, + bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 1, 0, 1].as_bitslice() + ); + virtual_overseer .send(FromOrchestra::Signal(OverseerSignal::ActiveLeaves( ActiveLeavesUpdate::stop_work(test_state.relay_parent), @@ -639,6 +695,107 @@ fn backing_works() { }); } +#[test] +fn extract_core_index_from_statement_works() { + let test_state = TestState::default(); + + let pov_a = PoV { block_data: BlockData(vec![42, 43, 44]) }; + let pvd_a = dummy_pvd(); + let validation_code_a = ValidationCode(vec![1, 2, 3]); + + let pov_hash = pov_a.hash(); + + let mut candidate = TestCandidateBuilder { + para_id: test_state.chain_ids[0], + relay_parent: test_state.relay_parent, + pov_hash, + erasure_root: make_erasure_root(&test_state, pov_a.clone(), pvd_a.clone()), + persisted_validation_data_hash: pvd_a.hash(), + validation_code: validation_code_a.0.clone(), + ..Default::default() + } + .build(); + + let public2 = Keystore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[2].to_seed()), + ) + .expect("Insert key into keystore"); + + let signed_statement_1 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate.clone(), pvd_a.clone()), + &test_state.signing_context, + ValidatorIndex(2), + &public2.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + let public1 = Keystore::sr25519_generate_new( + &*test_state.keystore, + ValidatorId::ID, + Some(&test_state.validators[1].to_seed()), + ) + .expect("Insert key into keystore"); + + let signed_statement_2 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate.clone(), pvd_a.clone()), + &test_state.signing_context, + ValidatorIndex(1), + &public1.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + candidate.descriptor.para_id = test_state.chain_ids[1]; + + let signed_statement_3 = SignedFullStatementWithPVD::sign( + &test_state.keystore, + StatementWithPVD::Seconded(candidate, pvd_a.clone()), + &test_state.signing_context, + ValidatorIndex(1), + &public1.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + + let core_index_1 = core_index_from_statement( + &test_state.validator_to_group, + &test_state.validator_groups.1, + &test_state.availability_cores, + &signed_statement_1, + ) + .unwrap(); + + assert_eq!(core_index_1, CoreIndex(0)); + + let core_index_2 = core_index_from_statement( + &test_state.validator_to_group, + &test_state.validator_groups.1, + &test_state.availability_cores, + &signed_statement_2, + ); + + // Must be none, para_id in descriptor is different than para assigned to core + assert_eq!(core_index_2, None); + + let core_index_3 = core_index_from_statement( + &test_state.validator_to_group, + &test_state.validator_groups.1, + &test_state.availability_cores, + &signed_statement_3, + ) + .unwrap(); + + assert_eq!(core_index_3, CoreIndex(1)); +} + #[test] fn backing_works_while_validation_ongoing() { let test_state = TestState::default(); @@ -801,20 +958,20 @@ fn backing_works_while_validation_ongoing() { let candidates = rx.await.unwrap(); assert_eq!(1, candidates.len()); - assert_eq!(candidates[0].validity_votes.len(), 3); + assert_eq!(candidates[0].validity_votes().len(), 3); assert!(candidates[0] - .validity_votes + .validity_votes() .contains(&ValidityAttestation::Implicit(signed_a.signature().clone()))); assert!(candidates[0] - .validity_votes + .validity_votes() .contains(&ValidityAttestation::Explicit(signed_b.signature().clone()))); assert!(candidates[0] - .validity_votes + .validity_votes() .contains(&ValidityAttestation::Explicit(signed_c.signature().clone()))); assert_eq!( - candidates[0].validator_indices, - bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 0, 1, 1], + candidates[0].validator_indices_and_core_index(false), + (bitvec::bitvec![u8, bitvec::order::Lsb0; 1, 0, 1, 1].as_bitslice(), None) ); virtual_overseer @@ -1422,7 +1579,7 @@ fn backing_works_after_failed_validation() { fn candidate_backing_reorders_votes() { use sp_core::Encode; - let para_id = ParaId::from(10); + let core_idx = CoreIndex(10); let validators = vec![ Sr25519Keyring::Alice, Sr25519Keyring::Bob, @@ -1436,7 +1593,7 @@ fn candidate_backing_reorders_votes() { let validator_groups = { let mut validator_groups = HashMap::new(); validator_groups - .insert(para_id, vec![0, 1, 2, 3, 4, 5].into_iter().map(ValidatorIndex).collect()); + .insert(core_idx, vec![0, 1, 2, 3, 4, 5].into_iter().map(ValidatorIndex).collect()); validator_groups }; @@ -1466,10 +1623,10 @@ fn candidate_backing_reorders_votes() { (ValidatorIndex(3), fake_attestation(3)), (ValidatorIndex(1), fake_attestation(1)), ], - group_id: para_id, + group_id: core_idx, }; - let backed = table_attested_to_backed(attested, &table_context).unwrap(); + let backed = table_attested_to_backed(attested, &table_context, false).unwrap(); let expected_bitvec = { let mut validator_indices = BitVec::::with_capacity(6); @@ -1486,8 +1643,11 @@ fn candidate_backing_reorders_votes() { let expected_attestations = vec![fake_attestation(1).into(), fake_attestation(3).into(), fake_attestation(5).into()]; - assert_eq!(backed.validator_indices, expected_bitvec); - assert_eq!(backed.validity_votes, expected_attestations); + assert_eq!( + backed.validator_indices_and_core_index(false), + (expected_bitvec.as_bitslice(), None) + ); + assert_eq!(backed.validity_votes(), expected_attestations); } // Test whether we retry on failed PoV fetching. diff --git a/polkadot/node/core/backing/src/tests/prospective_parachains.rs b/polkadot/node/core/backing/src/tests/prospective_parachains.rs index 578f21bef66515e49042d7a11692de67b9642d41..94310d2aa164650db84b78ddf361a9f465ac207d 100644 --- a/polkadot/node/core/backing/src/tests/prospective_parachains.rs +++ b/polkadot/node/core/backing/src/tests/prospective_parachains.rs @@ -185,6 +185,16 @@ async fn activate_leaf( } ); + // Node features request from runtime: all features are disabled. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::NodeFeatures(_session_index, tx)) + ) if parent == hash => { + tx.send(Ok(Default::default())).unwrap(); + } + ); + // Check if subsystem job issues a request for the minimum backing votes. assert_matches!( virtual_overseer.recv().await, @@ -305,10 +315,11 @@ async fn assert_hypothetical_frontier_requests( ) => { let idx = match expected_requests.iter().position(|r| r.0 == request) { Some(idx) => idx, - None => panic!( + None => + panic!( "unexpected hypothetical frontier request, no match found for {:?}", request - ), + ), }; let resp = std::mem::take(&mut expected_requests[idx].1); tx.send(resp).unwrap(); @@ -1268,6 +1279,7 @@ fn concurrent_dependent_candidates() { let statement_b = CandidateBackingMessage::Statement(leaf_parent, signed_b.clone()); virtual_overseer.send(FromOrchestra::Communication { msg: statement_a }).await; + // At this point the subsystem waits for response, the previous message is received, // send a second one without blocking. let _ = virtual_overseer @@ -1388,7 +1400,19 @@ fn concurrent_dependent_candidates() { assert_eq!(sess_idx, 1); tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _parent, + RuntimeApiRequest::ValidatorGroups(tx), + )) => { + tx.send(Ok(test_state.validator_groups.clone())).unwrap(); + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _parent, + RuntimeApiRequest::AvailabilityCores(tx), + )) => { + tx.send(Ok(test_state.availability_cores.clone())).unwrap(); + }, _ => panic!("unexpected message received from overseer: {:?}", msg), } } @@ -1419,7 +1443,6 @@ fn seconding_sanity_check_occupy_same_depth() { let leaf_parent = get_parent_hash(leaf_hash); let activated = new_leaf(leaf_hash, LEAF_BLOCK_NUMBER); - let min_block_number = LEAF_BLOCK_NUMBER - LEAF_ANCESTRY_LEN; let min_relay_parents = vec![(para_id_a, min_block_number), (para_id_b, min_block_number)]; let test_leaf_a = TestLeaf { activated, min_relay_parents }; @@ -1555,13 +1578,14 @@ fn occupied_core_assignment() { const LEAF_A_BLOCK_NUMBER: BlockNumber = 100; const LEAF_A_ANCESTRY_LEN: BlockNumber = 3; let para_id = test_state.chain_ids[0]; + let previous_para_id = test_state.chain_ids[1]; // Set the core state to occupied. let mut candidate_descriptor = ::test_helpers::dummy_candidate_descriptor(Hash::zero()); - candidate_descriptor.para_id = para_id; + candidate_descriptor.para_id = previous_para_id; test_state.availability_cores[0] = CoreState::Occupied(OccupiedCore { group_responsible: Default::default(), - next_up_on_available: None, + next_up_on_available: Some(ScheduledCore { para_id, collator: None }), occupied_since: 100_u32, time_out_at: 200_u32, next_up_on_time_out: None, diff --git a/polkadot/node/core/bitfield-signing/Cargo.toml b/polkadot/node/core/bitfield-signing/Cargo.toml index f42e8f8d22ba3d92e799bcd5aebe1cbeedca83de..6ecfffd7249b9213cc4ffb86f84a38e1cdb9babf 100644 --- a/polkadot/node/core/bitfield-signing/Cargo.toml +++ b/polkadot/node/core/bitfield-signing/Cargo.toml @@ -17,7 +17,7 @@ polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } wasm-timer = "0.2.5" -thiserror = "1.0.48" +thiserror = { workspace = true } [dev-dependencies] polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } diff --git a/polkadot/node/core/chain-selection/Cargo.toml b/polkadot/node/core/chain-selection/Cargo.toml index 27c2df85235e86d86860496923c3f89ce5fb0199..96fd42785cdd84ee463146f095b06ea01cc7b491 100644 --- a/polkadot/node/core/chain-selection/Cargo.toml +++ b/polkadot/node/core/chain-selection/Cargo.toml @@ -18,7 +18,7 @@ polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } kvdb = "0.13.0" -thiserror = "1.0.48" +thiserror = { workspace = true } parity-scale-codec = "3.6.1" [dev-dependencies] diff --git a/polkadot/node/core/dispute-coordinator/Cargo.toml b/polkadot/node/core/dispute-coordinator/Cargo.toml index 8a414a35f39cb9e1f3246e1afb83a5ea7a93cff1..1fff0a77170669753fa3fd8d852f936446fe7264 100644 --- a/polkadot/node/core/dispute-coordinator/Cargo.toml +++ b/polkadot/node/core/dispute-coordinator/Cargo.toml @@ -14,7 +14,7 @@ futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } parity-scale-codec = "3.6.1" kvdb = "0.13.0" -thiserror = "1.0.48" +thiserror = { workspace = true } schnellru = "0.2.1" fatality = "0.0.6" diff --git a/polkadot/node/core/dispute-coordinator/src/import.rs b/polkadot/node/core/dispute-coordinator/src/import.rs index 278561d5d00c1b9f4ec5d4e87287f76bebe8fb70..d3a4625f0d24b2ff0fc3bcc914d629e604909193 100644 --- a/polkadot/node/core/dispute-coordinator/src/import.rs +++ b/polkadot/node/core/dispute-coordinator/src/import.rs @@ -52,7 +52,8 @@ pub struct CandidateEnvironment<'a> { executor_params: &'a ExecutorParams, /// Validator indices controlled by this node. controlled_indices: HashSet, - /// Indices of disabled validators at the `relay_parent`. + /// Indices of on-chain disabled validators at the `relay_parent` combined + /// with the off-chain state. disabled_indices: HashSet, } @@ -67,16 +68,15 @@ impl<'a> CandidateEnvironment<'a> { runtime_info: &'a mut RuntimeInfo, session_index: SessionIndex, relay_parent: Hash, + disabled_offchain: impl IntoIterator, ) -> Option> { - let disabled_indices = runtime_info + let disabled_onchain = runtime_info .get_disabled_validators(ctx.sender(), relay_parent) .await .unwrap_or_else(|err| { gum::info!(target: LOG_TARGET, ?err, "Failed to get disabled validators"); Vec::new() - }) - .into_iter() - .collect(); + }); let (session, executor_params) = match runtime_info .get_session_info_by_index(ctx.sender(), relay_parent, session_index) @@ -87,6 +87,24 @@ impl<'a> CandidateEnvironment<'a> { Err(_) => return None, }; + let n_validators = session.validators.len(); + let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); + // combine on-chain with off-chain disabled validators + // process disabled validators in the following order: + // - on-chain disabled validators + // - prioritized order of off-chain disabled validators + // deduplicate the list and take at most `byzantine_threshold` validators + let disabled_indices = { + let mut d: HashSet = HashSet::new(); + for v in disabled_onchain.into_iter().chain(disabled_offchain.into_iter()) { + if d.len() == byzantine_threshold { + break + } + d.insert(v); + } + d + }; + let controlled_indices = find_controlled_validator_indices(keystore, &session.validators); Some(Self { session_index, session, executor_params, controlled_indices, disabled_indices }) } @@ -116,7 +134,7 @@ impl<'a> CandidateEnvironment<'a> { &self.controlled_indices } - /// Indices of disabled validators at the `relay_parent`. + /// Indices of off-chain and on-chain disabled validators. pub fn disabled_indices(&'a self) -> &'a HashSet { &self.disabled_indices } @@ -237,13 +255,19 @@ impl CandidateVoteState { let supermajority_threshold = polkadot_primitives::supermajority_threshold(n_validators); - // We have a dispute, if we have votes on both sides: - let is_disputed = !votes.invalid.is_empty() && !votes.valid.raw().is_empty(); + // We have a dispute, if we have votes on both sides, with at least one invalid vote + // from non-disabled validator or with votes on both sides and confirmed. + let has_non_disabled_invalid_votes = + votes.invalid.keys().any(|i| !env.disabled_indices().contains(i)); + let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); + let votes_on_both_sides = !votes.valid.raw().is_empty() && !votes.invalid.is_empty(); + let is_confirmed = + votes_on_both_sides && (votes.voted_indices().len() > byzantine_threshold); + let is_disputed = + is_confirmed || (has_non_disabled_invalid_votes && !votes.valid.raw().is_empty()); let (dispute_status, byzantine_threshold_against) = if is_disputed { let mut status = DisputeStatus::active(); - let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); - let is_confirmed = votes.voted_indices().len() > byzantine_threshold; if is_confirmed { status = status.confirm(); }; diff --git a/polkadot/node/core/dispute-coordinator/src/initialized.rs b/polkadot/node/core/dispute-coordinator/src/initialized.rs index a1bcc1f01707c434a7d58eaa801dfb762b573007..54e0410268f1b30f1e6627d4b2c89cac2b741ccb 100644 --- a/polkadot/node/core/dispute-coordinator/src/initialized.rs +++ b/polkadot/node/core/dispute-coordinator/src/initialized.rs @@ -17,7 +17,7 @@ //! Dispute coordinator subsystem in initialized state (after first active leaf is received). use std::{ - collections::{BTreeMap, HashSet, VecDeque}, + collections::{BTreeMap, VecDeque}, sync::Arc, }; @@ -970,6 +970,7 @@ impl Initialized { &mut self.runtime_info, session, relay_parent, + self.offchain_disabled_validators.iter(session), ) .await { @@ -1099,36 +1100,14 @@ impl Initialized { let new_state = import_result.new_state(); - let byzantine_threshold = polkadot_primitives::byzantine_threshold(n_validators); - // combine on-chain with off-chain disabled validators - // process disabled validators in the following order: - // - on-chain disabled validators - // - prioritized order of off-chain disabled validators - // deduplicate the list and take at most `byzantine_threshold` validators - let disabled_validators = { - let mut d: HashSet = HashSet::new(); - for v in env - .disabled_indices() - .iter() - .cloned() - .chain(self.offchain_disabled_validators.iter(session)) - { - if d.len() == byzantine_threshold { - break - } - d.insert(v); - } - d - }; - let is_included = self.scraper.is_candidate_included(&candidate_hash); let is_backed = self.scraper.is_candidate_backed(&candidate_hash); let own_vote_missing = new_state.own_vote_missing(); let is_disputed = new_state.is_disputed(); let is_confirmed = new_state.is_confirmed(); - let potential_spam = is_potential_spam(&self.scraper, &new_state, &candidate_hash, |v| { - disabled_validators.contains(v) - }); + let is_disabled = |v: &ValidatorIndex| env.disabled_indices().contains(v); + let potential_spam = + is_potential_spam(&self.scraper, &new_state, &candidate_hash, is_disabled); let allow_participation = !potential_spam; gum::trace!( @@ -1139,7 +1118,7 @@ impl Initialized { ?candidate_hash, confirmed = ?new_state.is_confirmed(), has_invalid_voters = ?!import_result.new_invalid_voters().is_empty(), - n_disabled_validators = ?disabled_validators.len(), + n_disabled_validators = ?env.disabled_indices().len(), "Is spam?" ); @@ -1439,6 +1418,7 @@ impl Initialized { &mut self.runtime_info, session, candidate_receipt.descriptor.relay_parent, + self.offchain_disabled_validators.iter(session), ) .await { diff --git a/polkadot/node/core/dispute-coordinator/src/lib.rs b/polkadot/node/core/dispute-coordinator/src/lib.rs index c3038fc0953c6bcc7cdc979f259f7e9bf80f3039..4b511e7430af655303a7bb6e5ca0e86d0f8e8c7b 100644 --- a/polkadot/node/core/dispute-coordinator/src/lib.rs +++ b/polkadot/node/core/dispute-coordinator/src/lib.rs @@ -341,6 +341,8 @@ impl DisputeCoordinatorSubsystem { runtime_info, highest_session, leaf_hash, + // on startup we don't have any off-chain disabled state + std::iter::empty(), ) .await { @@ -370,10 +372,9 @@ impl DisputeCoordinatorSubsystem { }, }; let vote_state = CandidateVoteState::new(votes, &env, now); - let onchain_disabled = env.disabled_indices(); - let potential_spam = is_potential_spam(&scraper, &vote_state, candidate_hash, |v| { - onchain_disabled.contains(v) - }); + let is_disabled = |v: &ValidatorIndex| env.disabled_indices().contains(v); + let potential_spam = + is_potential_spam(&scraper, &vote_state, candidate_hash, is_disabled); let is_included = scraper.is_candidate_included(&vote_state.votes().candidate_receipt.hash()); diff --git a/polkadot/node/core/dispute-coordinator/src/tests.rs b/polkadot/node/core/dispute-coordinator/src/tests.rs index af384256c4f71e2f78e4ac0baff2558b9ba27f46..0360e357bee4ca70fe9a6ab3a9454d611ddc93fd 100644 --- a/polkadot/node/core/dispute-coordinator/src/tests.rs +++ b/polkadot/node/core/dispute-coordinator/src/tests.rs @@ -2645,14 +2645,23 @@ fn participation_with_onchain_disabling_unconfirmed() { .await; handle_disabled_validators_queries(&mut virtual_overseer, vec![disabled_index]).await; - handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) - .await; - assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); // we should not participate due to disabled indices on chain assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none()); + { + // make sure the dispute is not active + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 0); + } + // Scenario 2: unconfirmed dispute with non-disabled validator against. // Expectation: even if the dispute is unconfirmed, we should participate // once we receive an invalid vote from a non-disabled validator. @@ -2679,6 +2688,9 @@ fn participation_with_onchain_disabling_unconfirmed() { }) .await; + handle_approval_vote_request(&mut virtual_overseer, &candidate_hash, HashMap::new()) + .await; + assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); participation_with_distribution( @@ -2710,7 +2722,7 @@ fn participation_with_onchain_disabling_unconfirmed() { .await; let (_, _, votes) = rx.await.unwrap().get(0).unwrap().clone(); - assert_eq!(votes.valid.raw().len(), 2); // 3+1 => we have participated + assert_eq!(votes.valid.raw().len(), 2); // 1+1 => we have participated assert_eq!(votes.invalid.len(), 2); } @@ -2832,6 +2844,7 @@ fn participation_with_onchain_disabling_confirmed() { #[test] fn participation_with_offchain_disabling() { + sp_tracing::init_for_tests(); test_harness(|mut test_state, mut virtual_overseer| { Box::pin(async move { let session = 1; @@ -2968,17 +2981,23 @@ fn participation_with_offchain_disabling() { // let's disable validators 3, 4 on chain, but this should not affect this import let disabled_validators = vec![ValidatorIndex(3), ValidatorIndex(4)]; handle_disabled_validators_queries(&mut virtual_overseer, disabled_validators).await; - handle_approval_vote_request( - &mut virtual_overseer, - &another_candidate_hash, - HashMap::new(), - ) - .await; assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); // we should not participate since due to offchain disabling assert!(virtual_overseer.recv().timeout(TEST_TIMEOUT).await.is_none()); + { + // make sure the new dispute is not active + let (tx, rx) = oneshot::channel(); + virtual_overseer + .send(FromOrchestra::Communication { + msg: DisputeCoordinatorMessage::ActiveDisputes(tx), + }) + .await; + + assert_eq!(rx.await.unwrap().len(), 1); + } + // now import enough votes for dispute confirmation // even though all of these votes are from (on chain) disabled validators let mut statements = Vec::new(); @@ -3007,6 +3026,12 @@ fn participation_with_offchain_disabling() { }) .await; + handle_approval_vote_request( + &mut virtual_overseer, + &another_candidate_hash, + HashMap::new(), + ) + .await; assert_eq!(confirmation_rx.await, Ok(ImportStatementsResult::ValidImport)); participation_with_distribution( diff --git a/polkadot/node/core/parachains-inherent/Cargo.toml b/polkadot/node/core/parachains-inherent/Cargo.toml index 3b2fa634102dd0537408a041443b301ffe073c08..24da4dc1e316f8469704ab57e4742dbc07197992 100644 --- a/polkadot/node/core/parachains-inherent/Cargo.toml +++ b/polkadot/node/core/parachains-inherent/Cargo.toml @@ -13,7 +13,7 @@ workspace = true futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../../gum" } -thiserror = "1.0.48" +thiserror = { workspace = true } async-trait = "0.1.74" polkadot-node-subsystem = { path = "../../subsystem" } polkadot-overseer = { path = "../../overseer" } diff --git a/polkadot/node/core/prospective-parachains/Cargo.toml b/polkadot/node/core/prospective-parachains/Cargo.toml index 5f0b2c0fdc96beb8c90a3e207307d4c9d5a2619b..5b62d90c1d4f82c885a650cfb63801358e57277d 100644 --- a/polkadot/node/core/prospective-parachains/Cargo.toml +++ b/polkadot/node/core/prospective-parachains/Cargo.toml @@ -13,7 +13,7 @@ workspace = true futures = "0.3.19" gum = { package = "tracing-gum", path = "../../gum" } parity-scale-codec = "3.6.4" -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" bitvec = "1" diff --git a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs b/polkadot/node/core/prospective-parachains/src/fragment_tree.rs index 292e4ebe5282b4344853ca5f3247caf1d9af6cc7..04ee42a9de062bbd183f92c360703eb12c25f1aa 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_tree.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_tree.rs @@ -202,7 +202,10 @@ impl CandidateStorage { /// Note that an existing candidate has been backed. pub fn mark_backed(&mut self, candidate_hash: &CandidateHash) { if let Some(entry) = self.by_candidate_hash.get_mut(candidate_hash) { + gum::trace!(target: LOG_TARGET, ?candidate_hash, "Candidate marked as backed"); entry.state = CandidateState::Backed; + } else { + gum::trace!(target: LOG_TARGET, ?candidate_hash, "Candidate not found while marking as backed"); } } @@ -753,53 +756,127 @@ impl FragmentTree { depths.iter_ones().collect() } - /// Select a candidate after the given `required_path` which passes - /// the predicate. - /// - /// If there are multiple possibilities, this will select the first one. + /// Select `count` candidates after the given `required_path` which pass + /// the predicate and have not already been backed on chain. /// - /// This returns `None` if there is no candidate meeting those criteria. + /// Does an exhaustive search into the tree starting after `required_path`. + /// If there are multiple possibilities of size `count`, this will select the first one. + /// If there is no chain of size `count` that matches the criteria, this will return the largest + /// chain it could find with the criteria. + /// If there are no candidates meeting those criteria, returns an empty `Vec`. + /// Cycles are accepted, see module docs for the `Cycles` section. /// /// The intention of the `required_path` is to allow queries on the basis of /// one or more candidates which were previously pending availability becoming /// available and opening up more room on the core. - pub(crate) fn select_child( + pub(crate) fn select_children( &self, required_path: &[CandidateHash], + count: u32, pred: impl Fn(&CandidateHash) -> bool, - ) -> Option { + ) -> Vec { let base_node = { // traverse the required path. let mut node = NodePointer::Root; for required_step in required_path { - node = self.node_candidate_child(node, &required_step)?; + if let Some(next_node) = self.node_candidate_child(node, &required_step) { + node = next_node; + } else { + return vec![] + }; } node }; - // TODO [now]: taking the first selection might introduce bias + // TODO: taking the first best selection might introduce bias // or become gameable. // // For plausibly unique parachains, this shouldn't matter much. // figure out alternative selection criteria? - match base_node { + self.select_children_inner(base_node, count, count, &pred, &mut vec![]) + } + + // Try finding a candidate chain starting from `base_node` of length `expected_count`. + // If not possible, return the longest one we could find. + // Does a depth-first search, since we're optimistic that there won't be more than one such + // chains (parachains shouldn't usually have forks). So in the usual case, this will conclude + // in `O(expected_count)`. + // Cycles are accepted, but this doesn't allow for infinite execution time, because the maximum + // depth we'll reach is `expected_count`. + // + // Worst case performance is `O(num_forks ^ expected_count)`. + // Although an exponential function, this is actually a constant that can only be altered via + // sudo/governance, because: + // 1. `num_forks` at a given level is at most `max_candidate_depth * max_validators_per_core` + // (because each validator in the assigned group can second `max_candidate_depth` + // candidates). The prospective-parachains subsystem assumes that the number of para forks is + // limited by collator-protocol and backing subsystems. In practice, this is a constant which + // can only be altered by sudo or governance. + // 2. `expected_count` is equal to the number of cores a para is scheduled on (in an elastic + // scaling scenario). For non-elastic-scaling, this is just 1. In practice, this should be a + // small number (1-3), capped by the total number of available cores (a constant alterable + // only via governance/sudo). + fn select_children_inner( + &self, + base_node: NodePointer, + expected_count: u32, + remaining_count: u32, + pred: &dyn Fn(&CandidateHash) -> bool, + accumulator: &mut Vec, + ) -> Vec { + if remaining_count == 0 { + // The best option is the chain we've accumulated so far. + return accumulator.to_vec(); + } + + let children: Vec<_> = match base_node { NodePointer::Root => self .nodes .iter() - .take_while(|n| n.parent == NodePointer::Root) - .filter(|n| self.scope.get_pending_availability(&n.candidate_hash).is_none()) - .filter(|n| pred(&n.candidate_hash)) - .map(|n| n.candidate_hash) - .next(), - NodePointer::Storage(ptr) => self.nodes[ptr] - .children - .iter() - .filter(|n| self.scope.get_pending_availability(&n.1).is_none()) - .filter(|n| pred(&n.1)) - .map(|n| n.1) - .next(), + .enumerate() + .take_while(|(_, n)| n.parent == NodePointer::Root) + .filter(|(_, n)| self.scope.get_pending_availability(&n.candidate_hash).is_none()) + .filter(|(_, n)| pred(&n.candidate_hash)) + .map(|(ptr, n)| (NodePointer::Storage(ptr), n.candidate_hash)) + .collect(), + NodePointer::Storage(base_node_ptr) => { + let base_node = &self.nodes[base_node_ptr]; + + base_node + .children + .iter() + .filter(|(_, hash)| self.scope.get_pending_availability(&hash).is_none()) + .filter(|(_, hash)| pred(&hash)) + .map(|(ptr, hash)| (*ptr, *hash)) + .collect() + }, + }; + + let mut best_result = accumulator.clone(); + for (child_ptr, child_hash) in children { + accumulator.push(child_hash); + + let result = self.select_children_inner( + child_ptr, + expected_count, + remaining_count - 1, + &pred, + accumulator, + ); + + accumulator.pop(); + + // Short-circuit the search if we've found the right length. Otherwise, we'll + // search for a max. + if result.len() == expected_count as usize { + return result + } else if best_result.len() < result.len() { + best_result = result; + } } + + best_result } fn populate_from_bases(&mut self, storage: &CandidateStorage, initial_bases: Vec) { @@ -984,6 +1061,7 @@ mod tests { use polkadot_node_subsystem_util::inclusion_emulator::InboundHrmpLimitations; use polkadot_primitives::{BlockNumber, CandidateCommitments, CandidateDescriptor, HeadData}; use polkadot_primitives_test_helpers as test_helpers; + use std::iter; fn make_constraints( min_relay_parent_number: BlockNumber, @@ -1521,6 +1599,21 @@ mod tests { assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); assert_eq!(tree.nodes[3].candidate_hash, candidate_a_hash); assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); + + for count in 1..10 { + assert_eq!( + tree.select_children(&[], count, |_| true), + iter::repeat(candidate_a_hash) + .take(std::cmp::min(count as usize, max_depth + 1)) + .collect::>() + ); + assert_eq!( + tree.select_children(&[candidate_a_hash], count - 1, |_| true), + iter::repeat(candidate_a_hash) + .take(std::cmp::min(count as usize - 1, max_depth)) + .collect::>() + ); + } } #[test] @@ -1588,6 +1681,35 @@ mod tests { assert_eq!(tree.nodes[2].candidate_hash, candidate_a_hash); assert_eq!(tree.nodes[3].candidate_hash, candidate_b_hash); assert_eq!(tree.nodes[4].candidate_hash, candidate_a_hash); + + assert_eq!(tree.select_children(&[], 1, |_| true), vec![candidate_a_hash],); + assert_eq!( + tree.select_children(&[], 2, |_| true), + vec![candidate_a_hash, candidate_b_hash], + ); + assert_eq!( + tree.select_children(&[], 3, |_| true), + vec![candidate_a_hash, candidate_b_hash, candidate_a_hash], + ); + assert_eq!( + tree.select_children(&[candidate_a_hash], 2, |_| true), + vec![candidate_b_hash, candidate_a_hash], + ); + + assert_eq!( + tree.select_children(&[], 6, |_| true), + vec![ + candidate_a_hash, + candidate_b_hash, + candidate_a_hash, + candidate_b_hash, + candidate_a_hash + ], + ); + assert_eq!( + tree.select_children(&[candidate_a_hash, candidate_b_hash], 6, |_| true), + vec![candidate_a_hash, candidate_b_hash, candidate_a_hash,], + ); } #[test] diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index dabcfb80e02ee7943885888aef183a327579e3bd..5937a1c1fb9fdc04eb2fa70e65f92f19d7b99bff 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -146,12 +146,20 @@ async fn run_iteration( handle_candidate_seconded(view, para, candidate_hash), ProspectiveParachainsMessage::CandidateBacked(para, candidate_hash) => handle_candidate_backed(&mut *ctx, view, para, candidate_hash).await?, - ProspectiveParachainsMessage::GetBackableCandidate( + ProspectiveParachainsMessage::GetBackableCandidates( relay_parent, para, + count, required_path, tx, - ) => answer_get_backable_candidate(&view, relay_parent, para, required_path, tx), + ) => answer_get_backable_candidates( + &view, + relay_parent, + para, + count, + required_path, + tx, + ), ProspectiveParachainsMessage::GetHypotheticalFrontier(request, tx) => answer_hypothetical_frontier_request(&view, request, tx), ProspectiveParachainsMessage::GetTreeMembership(para, candidate, tx) => @@ -290,7 +298,7 @@ async fn handle_active_leaves_update( ) .expect("ancestors are provided in reverse order and correctly; qed"); - gum::debug!( + gum::trace!( target: LOG_TARGET, relay_parent = ?hash, min_relay_parent = scope.earliest_relay_parent().number, @@ -552,12 +560,13 @@ async fn handle_candidate_backed( Ok(()) } -fn answer_get_backable_candidate( +fn answer_get_backable_candidates( view: &View, relay_parent: Hash, para: ParaId, + count: u32, required_path: Vec, - tx: oneshot::Sender>, + tx: oneshot::Sender>, ) { let data = match view.active_leaves.get(&relay_parent) { None => { @@ -568,7 +577,7 @@ fn answer_get_backable_candidate( "Requested backable candidate for inactive relay-parent." ); - let _ = tx.send(None); + let _ = tx.send(vec![]); return }, Some(d) => d, @@ -583,7 +592,7 @@ fn answer_get_backable_candidate( "Requested backable candidate for inactive para." ); - let _ = tx.send(None); + let _ = tx.send(vec![]); return }, Some(tree) => tree, @@ -598,30 +607,49 @@ fn answer_get_backable_candidate( "No candidate storage for active para", ); - let _ = tx.send(None); + let _ = tx.send(vec![]); return }, Some(s) => s, }; - let Some(child_hash) = - tree.select_child(&required_path, |candidate| storage.is_backed(candidate)) - else { - let _ = tx.send(None); - return - }; - let Some(candidate_relay_parent) = storage.relay_parent_by_candidate_hash(&child_hash) else { - gum::error!( + let backable_candidates: Vec<_> = tree + .select_children(&required_path, count, |candidate| storage.is_backed(candidate)) + .into_iter() + .filter_map(|child_hash| { + storage.relay_parent_by_candidate_hash(&child_hash).map_or_else( + || { + gum::error!( + target: LOG_TARGET, + ?child_hash, + para_id = ?para, + "Candidate is present in fragment tree but not in candidate's storage!", + ); + None + }, + |parent_hash| Some((child_hash, parent_hash)), + ) + }) + .collect(); + + if backable_candidates.is_empty() { + gum::trace!( target: LOG_TARGET, - ?child_hash, + ?required_path, para_id = ?para, - "Candidate is present in fragment tree but not in candidate's storage!", + %relay_parent, + "Could not find any backable candidate", ); - let _ = tx.send(None); - return - }; + } else { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?backable_candidates, + "Found backable candidates", + ); + } - let _ = tx.send(Some((child_hash, candidate_relay_parent))); + let _ = tx.send(backable_candidates); } fn answer_hypothetical_frontier_request( diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs index 7e369245c0e1587b405eb4516343610aa8c9a320..732736b101de0ce525c7753af4b60cca7534522b 100644 --- a/polkadot/node/core/prospective-parachains/src/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/tests.rs @@ -403,25 +403,28 @@ async fn get_membership( assert_eq!(resp, expected_membership_response); } -async fn get_backable_candidate( +async fn get_backable_candidates( virtual_overseer: &mut VirtualOverseer, leaf: &TestLeaf, para_id: ParaId, required_path: Vec, - expected_result: Option<(CandidateHash, Hash)>, + count: u32, + expected_result: Vec<(CandidateHash, Hash)>, ) { let (tx, rx) = oneshot::channel(); virtual_overseer .send(overseer::FromOrchestra::Communication { - msg: ProspectiveParachainsMessage::GetBackableCandidate( + msg: ProspectiveParachainsMessage::GetBackableCandidates( leaf.hash, para_id, + count, required_path, tx, ), }) .await; let resp = rx.await.unwrap(); + assert_eq!(resp.len(), expected_result.len()); assert_eq!(resp, expected_result); } @@ -849,9 +852,9 @@ fn check_candidate_on_multiple_forks() { assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); } -// Backs some candidates and tests `GetBackableCandidate`. +// Backs some candidates and tests `GetBackableCandidates` when requesting a single candidate. #[test] -fn check_backable_query() { +fn check_backable_query_single_candidate() { let test_state = TestState::default(); let view = test_harness(|mut virtual_overseer| async move { // Leaf A @@ -896,26 +899,38 @@ fn check_backable_query() { introduce_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await; // Should not get any backable candidates. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![candidate_hash_a], - None, + 1, + vec![], ) .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 0, + vec![], + ) + .await; + get_backable_candidates(&mut virtual_overseer, &leaf_a, 1.into(), vec![], 0, vec![]).await; // Second candidates. second_candidate(&mut virtual_overseer, candidate_a.clone()).await; second_candidate(&mut virtual_overseer, candidate_b.clone()).await; // Should not get any backable candidates. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![candidate_hash_a], - None, + 1, + vec![], ) .await; @@ -923,31 +938,46 @@ fn check_backable_query() { back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await; + // Should not get any backable candidates for the other para. + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 1, vec![]).await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + vec![candidate_hash_a], + 1, + vec![], + ) + .await; + // Get backable candidate. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![], - Some((candidate_hash_a, leaf_a.hash)), + 1, + vec![(candidate_hash_a, leaf_a.hash)], ) .await; - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![candidate_hash_a], - Some((candidate_hash_b, leaf_a.hash)), + 1, + vec![(candidate_hash_b, leaf_a.hash)], ) .await; // Should not get anything at the wrong path. - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, 1.into(), vec![candidate_hash_b], - None, + 1, + vec![], ) .await; @@ -961,6 +991,572 @@ fn check_backable_query() { assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); } +// Backs some candidates and tests `GetBackableCandidates` when requesting a multiple candidates. +#[test] +fn check_backable_query_multiple_candidates() { + macro_rules! make_and_back_candidate { + ($test_state:ident, $virtual_overseer:ident, $leaf:ident, $parent:expr, $index:expr) => {{ + let (mut candidate, pvd) = make_candidate( + $leaf.hash, + $leaf.number, + 1.into(), + $parent.commitments.head_data.clone(), + HeadData(vec![$index]), + $test_state.validation_code_hash, + ); + // Set a field to make this candidate unique. + candidate.descriptor.para_head = Hash::from_low_u64_le($index); + let candidate_hash = candidate.hash(); + introduce_candidate(&mut $virtual_overseer, candidate.clone(), pvd).await; + second_candidate(&mut $virtual_overseer, candidate.clone()).await; + back_candidate(&mut $virtual_overseer, &candidate, candidate_hash).await; + + (candidate, candidate_hash) + }}; + } + + // Parachain 1 looks like this: + // +---A----+ + // | | + // +----B---+ C + // | | | | + // D E F H + // | | + // G I + // | + // J + { + let test_state = TestState::default(); + let view = test_harness(|mut virtual_overseer| async move { + // Leaf A + let leaf_a = TestLeaf { + number: 100, + hash: Hash::from_low_u64_be(130), + para_data: vec![ + (1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))), + (2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))), + ], + }; + + // Activate leaves. + activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + + // Candidate A + let (candidate_a, pvd_a) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![1]), + test_state.validation_code_hash, + ); + let candidate_hash_a = candidate_a.hash(); + introduce_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await; + second_candidate(&mut virtual_overseer, candidate_a.clone()).await; + back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; + + let (candidate_b, candidate_hash_b) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 2); + let (candidate_c, candidate_hash_c) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 3); + let (_candidate_d, candidate_hash_d) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 4); + let (_candidate_e, candidate_hash_e) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 5); + let (candidate_f, candidate_hash_f) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 6); + let (_candidate_g, candidate_hash_g) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_f, 7); + let (candidate_h, candidate_hash_h) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_c, 8); + let (candidate_i, candidate_hash_i) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_h, 9); + let (_candidate_j, candidate_hash_j) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_i, 10); + + // Should not get any backable candidates for the other para. + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 1, vec![]) + .await; + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 5, vec![]) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + vec![candidate_hash_a], + 1, + vec![], + ) + .await; + + // Test various scenarios with various counts. + + // empty required_path + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 1, + vec![(candidate_hash_a, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 4, + vec![ + (candidate_hash_a, leaf_a.hash), + (candidate_hash_b, leaf_a.hash), + (candidate_hash_f, leaf_a.hash), + (candidate_hash_g, leaf_a.hash), + ], + ) + .await; + } + + // required path of 1 + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 1, + vec![(candidate_hash_b, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 2, + vec![(candidate_hash_b, leaf_a.hash), (candidate_hash_d, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 3, + vec![ + (candidate_hash_b, leaf_a.hash), + (candidate_hash_f, leaf_a.hash), + (candidate_hash_g, leaf_a.hash), + ], + ) + .await; + + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 5..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + count, + vec![ + (candidate_hash_c, leaf_a.hash), + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + (candidate_hash_j, leaf_a.hash), + ], + ) + .await; + } + } + + // required path of 2 + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_b], + 1, + vec![(candidate_hash_d, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c], + 1, + vec![(candidate_hash_h, leaf_a.hash)], + ) + .await; + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 4..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c], + count, + vec![ + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + (candidate_hash_j, leaf_a.hash), + ], + ) + .await; + } + } + + // No more candidates in any chain. + { + let required_paths = vec![ + vec![candidate_hash_a, candidate_hash_b, candidate_hash_e], + vec![ + candidate_hash_a, + candidate_hash_c, + candidate_hash_h, + candidate_hash_i, + candidate_hash_j, + ], + ]; + for path in required_paths { + for count in 1..4 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + path.clone(), + count, + vec![], + ) + .await; + } + } + } + + // Should not get anything at the wrong path. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b], + 1, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b, candidate_hash_a], + 3, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_b, candidate_hash_c], + 3, + vec![], + ) + .await; + + virtual_overseer + }); + + assert_eq!(view.active_leaves.len(), 1); + assert_eq!(view.candidate_storage.len(), 2); + // 10 candidates and 7 parents on para 1. + assert_eq!(view.candidate_storage.get(&1.into()).unwrap().len(), (7, 10)); + assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); + } + + // A tree with multiple roots. + // Parachain 1 looks like this: + // (imaginary root) + // | | + // +----B---+ A + // | | | | + // | | | C + // D E F | + // | H + // G | + // I + // | + // J + { + let test_state = TestState::default(); + let view = test_harness(|mut virtual_overseer| async move { + // Leaf A + let leaf_a = TestLeaf { + number: 100, + hash: Hash::from_low_u64_be(130), + para_data: vec![ + (1.into(), PerParaData::new(97, HeadData(vec![1, 2, 3]))), + (2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))), + ], + }; + + // Activate leaves. + activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + + // Candidate B + let (candidate_b, pvd_b) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![2]), + test_state.validation_code_hash, + ); + let candidate_hash_b = candidate_b.hash(); + introduce_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b).await; + second_candidate(&mut virtual_overseer, candidate_b.clone()).await; + back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await; + + // Candidate A + let (candidate_a, pvd_a) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![1]), + test_state.validation_code_hash, + ); + let candidate_hash_a = candidate_a.hash(); + introduce_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a).await; + second_candidate(&mut virtual_overseer, candidate_a.clone()).await; + back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; + + let (candidate_c, candidate_hash_c) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_a, 3); + let (_candidate_d, candidate_hash_d) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 4); + let (_candidate_e, candidate_hash_e) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 5); + let (candidate_f, candidate_hash_f) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_b, 6); + let (_candidate_g, candidate_hash_g) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_f, 7); + let (candidate_h, candidate_hash_h) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_c, 8); + let (candidate_i, candidate_hash_i) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_h, 9); + let (_candidate_j, candidate_hash_j) = + make_and_back_candidate!(test_state, virtual_overseer, leaf_a, &candidate_i, 10); + + // Should not get any backable candidates for the other para. + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 1, vec![]) + .await; + get_backable_candidates(&mut virtual_overseer, &leaf_a, 2.into(), vec![], 5, vec![]) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 2.into(), + vec![candidate_hash_a], + 1, + vec![], + ) + .await; + + // Test various scenarios with various counts. + + // empty required_path + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 1, + vec![(candidate_hash_b, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 2, + vec![(candidate_hash_b, leaf_a.hash), (candidate_hash_d, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![], + 4, + vec![ + (candidate_hash_a, leaf_a.hash), + (candidate_hash_c, leaf_a.hash), + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + ], + ) + .await; + } + + // required path of 1 + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 1, + vec![(candidate_hash_c, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b], + 1, + vec![(candidate_hash_d, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a], + 2, + vec![(candidate_hash_c, leaf_a.hash), (candidate_hash_h, leaf_a.hash)], + ) + .await; + + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 2..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b], + count, + vec![(candidate_hash_f, leaf_a.hash), (candidate_hash_g, leaf_a.hash)], + ) + .await; + } + } + + // required path of 2 + { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b, candidate_hash_f], + 1, + vec![(candidate_hash_g, leaf_a.hash)], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c], + 1, + vec![(candidate_hash_h, leaf_a.hash)], + ) + .await; + // If the requested count exceeds the largest chain, return the longest + // chain we can get. + for count in 4..10 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c], + count, + vec![ + (candidate_hash_h, leaf_a.hash), + (candidate_hash_i, leaf_a.hash), + (candidate_hash_j, leaf_a.hash), + ], + ) + .await; + } + } + + // No more candidates in any chain. + { + let required_paths = vec![ + vec![candidate_hash_b, candidate_hash_f, candidate_hash_g], + vec![candidate_hash_b, candidate_hash_e], + vec![candidate_hash_b, candidate_hash_d], + vec![ + candidate_hash_a, + candidate_hash_c, + candidate_hash_h, + candidate_hash_i, + candidate_hash_j, + ], + ]; + for path in required_paths { + for count in 1..4 { + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + path.clone(), + count, + vec![], + ) + .await; + } + } + } + + // Should not get anything at the wrong path. + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_d], + 1, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_b, candidate_hash_a], + 3, + vec![], + ) + .await; + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + vec![candidate_hash_a, candidate_hash_c, candidate_hash_d], + 3, + vec![], + ) + .await; + + virtual_overseer + }); + + assert_eq!(view.active_leaves.len(), 1); + assert_eq!(view.candidate_storage.len(), 2); + // 10 candidates and 7 parents on para 1. + assert_eq!(view.candidate_storage.get(&1.into()).unwrap().len(), (7, 10)); + assert_eq!(view.candidate_storage.get(&2.into()).unwrap().len(), (0, 0)); + } +} + // Test depth query. #[test] fn check_hypothetical_frontier_query() { @@ -1448,12 +2044,13 @@ fn persists_pending_availability_candidate() { second_candidate(&mut virtual_overseer, candidate_b.clone()).await; back_candidate(&mut virtual_overseer, &candidate_b, candidate_hash_b).await; - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_b, para_id, vec![candidate_hash_a], - Some((candidate_hash_b, leaf_b_hash)), + 1, + vec![(candidate_hash_b, leaf_b_hash)], ) .await; @@ -1512,12 +2109,13 @@ fn backwards_compatible() { second_candidate(&mut virtual_overseer, candidate_a.clone()).await; back_candidate(&mut virtual_overseer, &candidate_a, candidate_hash_a).await; - get_backable_candidate( + get_backable_candidates( &mut virtual_overseer, &leaf_a, para_id, vec![], - Some((candidate_hash_a, candidate_relay_parent)), + 1, + vec![(candidate_hash_a, candidate_relay_parent)], ) .await; @@ -1537,7 +2135,7 @@ fn backwards_compatible() { ) .await; - get_backable_candidate(&mut virtual_overseer, &leaf_b, para_id, vec![], None).await; + get_backable_candidates(&mut virtual_overseer, &leaf_b, para_id, vec![], 1, vec![]).await; virtual_overseer }); diff --git a/polkadot/node/core/provisioner/Cargo.toml b/polkadot/node/core/provisioner/Cargo.toml index 175980e90a057f136de25ad7091bcaef497f0a5f..24cdfd6b57b373712bfdd05e897e2a6b0f1417b3 100644 --- a/polkadot/node/core/provisioner/Cargo.toml +++ b/polkadot/node/core/provisioner/Cargo.toml @@ -13,7 +13,7 @@ workspace = true bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } futures = "0.3.21" gum = { package = "tracing-gum", path = "../../gum" } -thiserror = "1.0.48" +thiserror = { workspace = true } polkadot-primitives = { path = "../../../primitives" } polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem = { path = "../../subsystem" } diff --git a/polkadot/node/core/provisioner/src/lib.rs b/polkadot/node/core/provisioner/src/lib.rs index 3970b8572612da827e7ddda18f46eadcf12f6906..a29cf72afb14e7ca949b025066daf1c875101427 100644 --- a/polkadot/node/core/provisioner/src/lib.rs +++ b/polkadot/node/core/provisioner/src/lib.rs @@ -681,10 +681,17 @@ async fn request_backable_candidates( CoreState::Free => continue, }; + // We should be calling this once per para rather than per core. + // TODO: Will be fixed in https://github.com/paritytech/polkadot-sdk/pull/3233. + // For now, at least make sure we don't supply the same candidate multiple times in case a + // para has multiple cores scheduled. let response = get_backable_candidate(relay_parent, para_id, required_path, sender).await?; - match response { - Some((hash, relay_parent)) => selected_candidates.push((hash, relay_parent)), + Some((hash, relay_parent)) => { + if !selected_candidates.iter().any(|bc| &(hash, relay_parent) == bc) { + selected_candidates.push((hash, relay_parent)) + } + }, None => { gum::debug!( target: LOG_TARGET, @@ -726,6 +733,7 @@ async fn select_candidates( ) .await?, }; + gum::debug!(target: LOG_TARGET, ?selected_candidates, "Got backable candidates"); // now get the backed candidates corresponding to these candidate receipts let (tx, rx) = oneshot::channel(); @@ -758,7 +766,7 @@ async fn select_candidates( // keep only one candidate with validation code. let mut with_validation_code = false; candidates.retain(|c| { - if c.candidate.commitments.new_validation_code.is_some() { + if c.candidate().commitments.new_validation_code.is_some() { if with_validation_code { return false } @@ -806,15 +814,18 @@ async fn get_backable_candidate( ) -> Result, Error> { let (tx, rx) = oneshot::channel(); sender - .send_message(ProspectiveParachainsMessage::GetBackableCandidate( + .send_message(ProspectiveParachainsMessage::GetBackableCandidates( relay_parent, para_id, + 1, // core count hardcoded to 1, until elastic scaling is implemented and enabled. required_path, tx, )) .await; - rx.await.map_err(Error::CanceledBackableCandidate) + rx.await + .map_err(Error::CanceledBackableCandidate) + .map(|res| res.get(0).copied()) } /// The availability bitfield for a given core is the transpose diff --git a/polkadot/node/core/provisioner/src/tests.rs b/polkadot/node/core/provisioner/src/tests.rs index 1d7bdfcfcb89c251cfdeb9c6669a5b07e8b05985..87c0e7a65d35310341fe7df3e6b2cb37239b7313 100644 --- a/polkadot/node/core/provisioner/src/tests.rs +++ b/polkadot/node/core/provisioner/src/tests.rs @@ -373,13 +373,18 @@ mod select_candidates { let _ = sender.send(response); }, AllMessages::ProspectiveParachains( - ProspectiveParachainsMessage::GetBackableCandidate(.., tx), - ) => match prospective_parachains_mode { - ProspectiveParachainsMode::Enabled { .. } => { - let _ = tx.send(candidates_iter.next()); - }, - ProspectiveParachainsMode::Disabled => - panic!("unexpected prospective parachains request"), + ProspectiveParachainsMessage::GetBackableCandidates(_, _, count, _, tx), + ) => { + assert_eq!(count, 1); + + match prospective_parachains_mode { + ProspectiveParachainsMode::Enabled { .. } => { + let _ = + tx.send(candidates_iter.next().map_or_else(Vec::new, |c| vec![c])); + }, + ProspectiveParachainsMode::Disabled => + panic!("unexpected prospective parachains request"), + } }, _ => panic!("Unexpected message: {:?}", from_job), } @@ -455,13 +460,16 @@ mod select_candidates { let expected_backed = expected_candidates .iter() - .map(|c| BackedCandidate { - candidate: CommittedCandidateReceipt { - descriptor: c.descriptor.clone(), - commitments: Default::default(), - }, - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|c| { + BackedCandidate::new( + CommittedCandidateReceipt { + descriptor: c.descriptor().clone(), + commitments: Default::default(), + }, + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); @@ -481,7 +489,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_candidates.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) @@ -527,10 +535,13 @@ mod select_candidates { // Build possible outputs from select_candidates let backed_candidates: Vec<_> = committed_receipts .iter() - .map(|committed_receipt| BackedCandidate { - candidate: committed_receipt.clone(), - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|committed_receipt| { + BackedCandidate::new( + committed_receipt.clone(), + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); @@ -561,7 +572,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_backed_filtered.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_backed_filtered.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) @@ -600,13 +611,16 @@ mod select_candidates { let expected_backed = expected_candidates .iter() - .map(|c| BackedCandidate { - candidate: CommittedCandidateReceipt { - descriptor: c.descriptor.clone(), - commitments: Default::default(), - }, - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|c| { + BackedCandidate::new( + CommittedCandidateReceipt { + descriptor: c.descriptor.clone(), + commitments: Default::default(), + }, + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); @@ -626,7 +640,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_candidates.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) @@ -666,13 +680,16 @@ mod select_candidates { let expected_backed = expected_candidates .iter() - .map(|c| BackedCandidate { - candidate: CommittedCandidateReceipt { - descriptor: c.descriptor.clone(), - commitments: Default::default(), - }, - validity_votes: Vec::new(), - validator_indices: default_bitvec(MOCK_GROUP_SIZE), + .map(|c| { + BackedCandidate::new( + CommittedCandidateReceipt { + descriptor: c.descriptor().clone(), + commitments: Default::default(), + }, + Vec::new(), + default_bitvec(MOCK_GROUP_SIZE), + None, + ) }) .collect(); @@ -692,7 +709,7 @@ mod select_candidates { result.into_iter().for_each(|c| { assert!( - expected_candidates.iter().any(|c2| c.candidate.corresponds_to(c2)), + expected_candidates.iter().any(|c2| c.candidate().corresponds_to(c2)), "Failed to find candidate: {:?}", c, ) diff --git a/polkadot/node/core/pvf-checker/Cargo.toml b/polkadot/node/core/pvf-checker/Cargo.toml index 3f02c2fa74a8e09248a586187f342ff5db2e6146..f4f954e316c0b758a4cb1e94f80b729257349632 100644 --- a/polkadot/node/core/pvf-checker/Cargo.toml +++ b/polkadot/node/core/pvf-checker/Cargo.toml @@ -11,7 +11,7 @@ workspace = true [dependencies] futures = "0.3.21" -thiserror = "1.0.48" +thiserror = { workspace = true } gum = { package = "tracing-gum", path = "../../gum" } polkadot-node-primitives = { path = "../../primitives" } diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index d7ea573cfd2f3e772a3af04c0b840ce94ecdf93a..9ed64b88ffde857c6abe171a7d1cad7dd66dd509 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -23,7 +23,7 @@ pin-project = "1.0.9" rand = "0.8.5" slotmap = "1.0" tempfile = "3.3.0" -thiserror = "1.0.31" +thiserror = { workspace = true } tokio = { version = "1.24.2", features = ["fs", "process"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/polkadot/node/core/pvf/README.md b/polkadot/node/core/pvf/README.md index 796e17c05faa47ceec455125ae29f7943ffa5740..5304b0720b2df614c005ff54eabb4d2c74b0bb4b 100644 --- a/polkadot/node/core/pvf/README.md +++ b/polkadot/node/core/pvf/README.md @@ -13,7 +13,7 @@ See also: Running `cargo test` in the `pvf/` directory will run unit and integration tests. -**Note:** some tests run only under Linux, amd64, and/or with the +**Note:** some tests run only under Linux, x86-64, and/or with the `ci-only-tests` feature enabled. See the general [Testing][testing] instructions for more information on @@ -34,8 +34,8 @@ RUST_LOG=parachain::pvf=trace zombienet --provider=native spawn zombienet_tests/ ## Testing on Linux Some of the PVF functionality, especially related to security, is Linux-only, -and some is amd64-only. If you touch anything security-related, make sure to -test on Linux amd64! If you're on a Mac, you can either run a VM or you can hire +and some is x86-64-only. If you touch anything security-related, make sure to +test on Linux x86-64! If you're on a Mac, you can either run a VM or you can hire a VPS and use the open-source tool [EternalTerminal][et] to connect to it.[^et] [^et]: Unlike ssh, ET preserves your session across disconnects, and unlike diff --git a/polkadot/node/core/pvf/common/Cargo.toml b/polkadot/node/core/pvf/common/Cargo.toml index 583db0b29f45aeb1c04f1d393b856b96bb41b92e..56bad9792fa0b6accea6460025c64c18e8e1e5d8 100644 --- a/polkadot/node/core/pvf/common/Cargo.toml +++ b/polkadot/node/core/pvf/common/Cargo.toml @@ -15,7 +15,7 @@ cpu-time = "1.0.0" futures = "0.3.21" gum = { package = "tracing-gum", path = "../../../gum" } libc = "0.2.152" -thiserror = "1.0.31" +thiserror = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } @@ -35,6 +35,8 @@ sp-tracing = { path = "../../../../../substrate/primitives/tracing" } [target.'cfg(target_os = "linux")'.dependencies] landlock = "0.3.0" nix = { version = "0.27.1", features = ["sched"] } + +[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies] seccompiler = "0.4.0" [dev-dependencies] diff --git a/polkadot/node/core/pvf/common/src/lib.rs b/polkadot/node/core/pvf/common/src/lib.rs index d891c06bf2ada3efedbdd9420119c61fb96f5791..15097dbd3af5c29d1fceabc8d04f6ee9a395ef30 100644 --- a/polkadot/node/core/pvf/common/src/lib.rs +++ b/polkadot/node/core/pvf/common/src/lib.rs @@ -86,3 +86,33 @@ pub fn framed_recv_blocking(r: &mut (impl Read + Unpin)) -> io::Result> r.read_exact(&mut buf)?; Ok(buf) } + +#[cfg(all(test, not(feature = "test-utils")))] +mod tests { + use super::*; + + #[test] + fn default_secure_status() { + let status = SecurityStatus::default(); + assert!( + !status.secure_validator_mode, + "secure_validator_mode is false for default security status" + ); + assert!( + !status.can_enable_landlock, + "can_enable_landlock is false for default security status" + ); + assert!( + !status.can_enable_seccomp, + "can_enable_seccomp is false for default security status" + ); + assert!( + !status.can_unshare_user_namespace_and_change_root, + "can_unshare_user_namespace_and_change_root is false for default security status" + ); + assert!( + !status.can_do_secure_clone, + "can_do_secure_clone is false for default security status" + ); + } +} diff --git a/polkadot/node/core/pvf/src/host.rs b/polkadot/node/core/pvf/src/host.rs index e215f11b91d396a1b9e5fa5710d34acf28ca9cff..ae9fdc7d2dea81ac85c8c4ad752fca6fbf9a6f55 100644 --- a/polkadot/node/core/pvf/src/host.rs +++ b/polkadot/node/core/pvf/src/host.rs @@ -24,7 +24,7 @@ use crate::{ artifacts::{ArtifactId, ArtifactPathId, ArtifactState, Artifacts}, execute::{self, PendingExecutionRequest}, metrics::Metrics, - prepare, security, Priority, SecurityStatus, ValidationError, LOG_TARGET, + prepare, Priority, SecurityStatus, ValidationError, LOG_TARGET, }; use always_assert::never; use futures::{ @@ -225,10 +225,32 @@ pub async fn start( // Run checks for supported security features once per host startup. If some checks fail, warn // if Secure Validator Mode is disabled and return an error otherwise. - let security_status = match security::check_security_status(&config).await { + #[cfg(target_os = "linux")] + let security_status = match crate::security::check_security_status(&config).await { Ok(ok) => ok, Err(err) => return Err(SubsystemError::Context(err)), }; + #[cfg(not(target_os = "linux"))] + let security_status = if config.secure_validator_mode { + gum::error!( + target: LOG_TARGET, + "{}{}{}", + crate::SECURE_MODE_ERROR, + crate::SECURE_LINUX_NOTE, + crate::IGNORE_SECURE_MODE_TIP + ); + return Err(SubsystemError::Context( + "could not enable Secure Validator Mode for non-Linux; check logs".into(), + )); + } else { + gum::warn!( + target: LOG_TARGET, + "{}{}", + crate::SECURE_MODE_WARNING, + crate::SECURE_LINUX_NOTE, + ); + SecurityStatus::default() + }; let (to_host_tx, to_host_rx) = mpsc::channel(HOST_MESSAGE_QUEUE_SIZE); diff --git a/polkadot/node/core/pvf/src/lib.rs b/polkadot/node/core/pvf/src/lib.rs index 92263281eeab1c5f0cf96dad249db4c110c43114..462498fa8f6b108c6057b378d2a0b07face31ec9 100644 --- a/polkadot/node/core/pvf/src/lib.rs +++ b/polkadot/node/core/pvf/src/lib.rs @@ -97,6 +97,7 @@ mod host; mod metrics; mod prepare; mod priority; +#[cfg(target_os = "linux")] mod security; mod worker_interface; @@ -135,3 +136,22 @@ pub fn get_worker_version(worker_path: &Path) -> std::io::Result { .trim() .to_string()) } + +// Trying to run securely and some mandatory errors occurred. +pub(crate) const SECURE_MODE_ERROR: &'static str = + "🚨 Your system cannot securely run a validator. \ +\nRunning validation of malicious PVF code has a higher risk of compromising this machine."; +// Some errors occurred when running insecurely, or some optional errors occurred when running +// securely. +pub(crate) const SECURE_MODE_WARNING: &'static str = "🚨 Some security issues have been detected. \ +\nRunning validation of malicious PVF code has a higher risk of compromising this machine."; +// Message to be printed only when running securely and mandatory errors occurred. +pub(crate) const IGNORE_SECURE_MODE_TIP: &'static str = +"\nYou can ignore this error with the `--insecure-validator-i-know-what-i-do` \ +command line argument if you understand and accept the risks of running insecurely. \ +With this flag, security features are enabled on a best-effort basis, but not mandatory. \ +\nMore information: https://wiki.polkadot.network/docs/maintain-guides-secure-validator#secure-validator-mode"; +// Only Linux supports security features +#[cfg(not(target_os = "linux"))] +pub(crate) const SECURE_LINUX_NOTE: &'static str = "\nSecure mode is enabled only for Linux \ +\nand a full secure mode is enabled only for Linux x86-64."; diff --git a/polkadot/node/core/pvf/src/security.rs b/polkadot/node/core/pvf/src/security.rs index f62a232abf27a3745e0622094686d56b07e16cc1..733ef18bcadb497020db70f54db72d507a750c58 100644 --- a/polkadot/node/core/pvf/src/security.rs +++ b/polkadot/node/core/pvf/src/security.rs @@ -169,20 +169,6 @@ impl fmt::Display for SecureModeError { /// Print an error if Secure Validator Mode and some mandatory errors occurred, warn otherwise. fn print_secure_mode_error_or_warning(security_status: &FullSecurityStatus) { - // Trying to run securely and some mandatory errors occurred. - const SECURE_MODE_ERROR: &'static str = "🚨 Your system cannot securely run a validator. \ - \nRunning validation of malicious PVF code has a higher risk of compromising this machine."; - // Some errors occurred when running insecurely, or some optional errors occurred when running - // securely. - const SECURE_MODE_WARNING: &'static str = "🚨 Some security issues have been detected. \ - \nRunning validation of malicious PVF code has a higher risk of compromising this machine."; - // Message to be printed only when running securely and mandatory errors occurred. - const IGNORE_SECURE_MODE_TIP: &'static str = - "\nYou can ignore this error with the `--insecure-validator-i-know-what-i-do` \ - command line argument if you understand and accept the risks of running insecurely. \ - With this flag, security features are enabled on a best-effort basis, but not mandatory. \ - \nMore information: https://wiki.polkadot.network/docs/maintain-guides-secure-validator#secure-validator-mode"; - let all_errs_allowed = security_status.all_errs_allowed(); let errs_string = security_status.errs_string(); @@ -190,16 +176,16 @@ fn print_secure_mode_error_or_warning(security_status: &FullSecurityStatus) { gum::warn!( target: LOG_TARGET, "{}{}", - SECURE_MODE_WARNING, + crate::SECURE_MODE_WARNING, errs_string, ); } else { gum::error!( target: LOG_TARGET, "{}{}{}", - SECURE_MODE_ERROR, + crate::SECURE_MODE_ERROR, errs_string, - IGNORE_SECURE_MODE_TIP + crate::IGNORE_SECURE_MODE_TIP ); } } @@ -210,29 +196,25 @@ fn print_secure_mode_error_or_warning(security_status: &FullSecurityStatus) { /// to running the check in a worker, we try it... in a worker. The expected return status is 0 on /// success and -1 on failure. async fn check_can_unshare_user_namespace_and_change_root( - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] prepare_worker_program_path: &Path, - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] cache_path: &Path, + cache_path: &Path, ) -> SecureModeResult { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - let cache_dir_tempdir = tempfile::Builder::new() - .prefix("check-can-unshare-") - .tempdir_in(cache_path) - .map_err(|err| SecureModeError::CannotUnshareUserNamespaceAndChangeRoot( - format!("could not create a temporary directory in {:?}: {}", cache_path, err) - ))?; - spawn_process_for_security_check( - prepare_worker_program_path, - "--check-can-unshare-user-namespace-and-change-root", - &[cache_dir_tempdir.path()], - ).await.map_err(|err| SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(err)) - } else { - Err(SecureModeError::CannotUnshareUserNamespaceAndChangeRoot( - "only available on Linux".into() + let cache_dir_tempdir = tempfile::Builder::new() + .prefix("check-can-unshare-") + .tempdir_in(cache_path) + .map_err(|err| { + SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(format!( + "could not create a temporary directory in {:?}: {}", + cache_path, err )) - } - } + })?; + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-unshare-user-namespace-and-change-root", + &[cache_dir_tempdir.path()], + ) + .await + .map_err(|err| SecureModeError::CannotUnshareUserNamespaceAndChangeRoot(err)) } /// Check if landlock is supported and return an error if not. @@ -240,25 +222,15 @@ async fn check_can_unshare_user_namespace_and_change_root( /// We do this check by spawning a new process and trying to sandbox it. To get as close as possible /// to running the check in a worker, we try it... in a worker. The expected return status is 0 on /// success and -1 on failure. -async fn check_landlock( - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] - prepare_worker_program_path: &Path, -) -> SecureModeResult { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - let abi = polkadot_node_core_pvf_common::worker::security::landlock::LANDLOCK_ABI as u8; - spawn_process_for_security_check( - prepare_worker_program_path, - "--check-can-enable-landlock", - std::iter::empty::<&str>(), - ).await.map_err(|err| SecureModeError::CannotEnableLandlock { err, abi }) - } else { - Err(SecureModeError::CannotEnableLandlock { - err: "only available on Linux".into(), - abi: 0, - }) - } - } +async fn check_landlock(prepare_worker_program_path: &Path) -> SecureModeResult { + let abi = polkadot_node_core_pvf_common::worker::security::landlock::LANDLOCK_ABI as u8; + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-enable-landlock", + std::iter::empty::<&str>(), + ) + .await + .map_err(|err| SecureModeError::CannotEnableLandlock { err, abi }) } /// Check if seccomp is supported and return an error if not. @@ -266,39 +238,23 @@ async fn check_landlock( /// We do this check by spawning a new process and trying to sandbox it. To get as close as possible /// to running the check in a worker, we try it... in a worker. The expected return status is 0 on /// success and -1 on failure. -async fn check_seccomp( - #[cfg_attr(not(all(target_os = "linux", target_arch = "x86_64")), allow(unused_variables))] - prepare_worker_program_path: &Path, -) -> SecureModeResult { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - spawn_process_for_security_check( - prepare_worker_program_path, - "--check-can-enable-seccomp", - std::iter::empty::<&str>(), - ).await.map_err(|err| SecureModeError::CannotEnableSeccomp(err)) - } else { - Err(SecureModeError::CannotEnableSeccomp( - "only supported on CPUs from the x86_64 family (usually Intel or AMD)".into() - )) - } - } - } else { - cfg_if::cfg_if! { - if #[cfg(target_arch = "x86_64")] { - Err(SecureModeError::CannotEnableSeccomp( - "only supported on Linux".into() - )) - } else { - Err(SecureModeError::CannotEnableSeccomp( - "only supported on Linux and on CPUs from the x86_64 family (usually Intel or AMD).".into() - )) - } - } - } - } + +#[cfg(target_arch = "x86_64")] +async fn check_seccomp(prepare_worker_program_path: &Path) -> SecureModeResult { + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-enable-seccomp", + std::iter::empty::<&str>(), + ) + .await + .map_err(|err| SecureModeError::CannotEnableSeccomp(err)) +} + +#[cfg(not(target_arch = "x86_64"))] +async fn check_seccomp(_: &Path) -> SecureModeResult { + Err(SecureModeError::CannotEnableSeccomp( + "only supported on CPUs from the x86_64 family (usually Intel or AMD)".into(), + )) } /// Check if we can call `clone` with all sandboxing flags, and return an error if not. @@ -306,26 +262,16 @@ async fn check_seccomp( /// We do this check by spawning a new process and trying to sandbox it. To get as close as possible /// to running the check in a worker, we try it... in a worker. The expected return status is 0 on /// success and -1 on failure. -async fn check_can_do_secure_clone( - #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] - prepare_worker_program_path: &Path, -) -> SecureModeResult { - cfg_if::cfg_if! { - if #[cfg(target_os = "linux")] { - spawn_process_for_security_check( - prepare_worker_program_path, - "--check-can-do-secure-clone", - std::iter::empty::<&str>(), - ).await.map_err(|err| SecureModeError::CannotDoSecureClone(err)) - } else { - Err(SecureModeError::CannotDoSecureClone( - "only available on Linux".into() - )) - } - } +async fn check_can_do_secure_clone(prepare_worker_program_path: &Path) -> SecureModeResult { + spawn_process_for_security_check( + prepare_worker_program_path, + "--check-can-do-secure-clone", + std::iter::empty::<&str>(), + ) + .await + .map_err(|err| SecureModeError::CannotDoSecureClone(err)) } -#[cfg(target_os = "linux")] async fn spawn_process_for_security_check( prepare_worker_program_path: &Path, check_arg: &'static str, diff --git a/polkadot/node/gum/proc-macro/Cargo.toml b/polkadot/node/gum/proc-macro/Cargo.toml index 6fb38653e735428bad1f1da53c39fdc0863cbc6b..70126b4f43367ce11a1a23d462392b513ca1c028 100644 --- a/polkadot/node/gum/proc-macro/Cargo.toml +++ b/polkadot/node/gum/proc-macro/Cargo.toml @@ -16,8 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "2.0.48", features = ["extra-traits", "full"] } -quote = "1.0.28" +syn = { features = ["extra-traits", "full"], workspace = true } +quote = { workspace = true } proc-macro2 = "1.0.56" proc-macro-crate = "3.0.0" expander = "2.0.0" diff --git a/polkadot/node/jaeger/Cargo.toml b/polkadot/node/jaeger/Cargo.toml index 892292c714b11bba1114f7016706299065378de6..23ab8f8421084751fa61d48bd3aa2b346e5a75c1 100644 --- a/polkadot/node/jaeger/Cargo.toml +++ b/polkadot/node/jaeger/Cargo.toml @@ -17,7 +17,7 @@ polkadot-primitives = { path = "../../primitives" } polkadot-node-primitives = { path = "../primitives" } sc-network = { path = "../../../substrate/client/network" } sp-core = { path = "../../../substrate/primitives/core" } -thiserror = "1.0.48" +thiserror = { workspace = true } tokio = "1.24.2" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", default-features = false } diff --git a/polkadot/node/malus/Cargo.toml b/polkadot/node/malus/Cargo.toml index 6a3dff726ed328aecf37e4985ed0d7ec3eb07674..ea25b9077f3a7000cef89a328016fd95b80b9035 100644 --- a/polkadot/node/malus/Cargo.toml +++ b/polkadot/node/malus/Cargo.toml @@ -43,7 +43,7 @@ assert_matches = "1.5" async-trait = "0.1.74" sp-keystore = { path = "../../../substrate/primitives/keystore" } sp-core = { path = "../../../substrate/primitives/core" } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" gum = { package = "tracing-gum", path = "../gum" } diff --git a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs index 5e460f18903b7f387772bc847a9699ea2fd085d3..22b44ddd1dc359620b199118bebf0bb83b69dada 100644 --- a/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs +++ b/polkadot/node/malus/src/variants/suggest_garbage_candidate.rs @@ -22,6 +22,7 @@ #![allow(missing_docs)] +use futures::channel::oneshot; use polkadot_cli::{ service::{ AuthorityDiscoveryApi, AuxStore, BabeApi, Block, Error, ExtendedOverseerGenArgs, @@ -30,7 +31,6 @@ use polkadot_cli::{ }, validator_overseer_builder, Cli, }; -use polkadot_node_core_candidate_validation::find_validation_data; use polkadot_node_primitives::{AvailableData, BlockData, PoV}; use polkadot_node_subsystem_types::DefaultSubsystemClient; use polkadot_primitives::{CandidateDescriptor, CandidateReceipt}; @@ -82,7 +82,7 @@ where CandidateBackingMessage::Second( relay_parent, ref candidate, - ref _validation_data, + ref validation_data, ref _pov, ), } => { @@ -112,6 +112,7 @@ where let (sender, receiver) = std::sync::mpsc::channel(); let mut new_sender = subsystem_sender.clone(); let _candidate = candidate.clone(); + let validation_data = validation_data.clone(); self.spawner.spawn_blocking( "malus-get-validation-data", Some("malus"), @@ -124,22 +125,51 @@ where .unwrap() .len(); gum::trace!(target: MALUS, "Validators {}", n_validators); - match find_validation_data(&mut new_sender, &_candidate.descriptor()) - .await - { - Ok(Some((validation_data, validation_code))) => { - sender - .send(Some(( - validation_data, - validation_code, - n_validators, - ))) - .expect("channel is still open"); - }, - _ => { - sender.send(None).expect("channel is still open"); - }, - } + + let validation_code = { + let validation_code_hash = + _candidate.descriptor().validation_code_hash; + let (tx, rx) = oneshot::channel(); + new_sender + .send_message(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::ValidationCodeByHash( + validation_code_hash, + tx, + ), + )) + .await; + + let code = rx.await.expect("Querying the RuntimeApi should work"); + match code { + Err(e) => { + gum::error!( + target: MALUS, + ?validation_code_hash, + error = %e, + "Failed to fetch validation code", + ); + + sender.send(None).expect("channel is still open"); + return + }, + Ok(None) => { + gum::debug!( + target: MALUS, + ?validation_code_hash, + "Could not find validation code on chain", + ); + + sender.send(None).expect("channel is still open"); + return + }, + Ok(Some(c)) => c, + } + }; + + sender + .send(Some((validation_data, validation_code, n_validators))) + .expect("channel is still open"); }), ); diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index 7d67c0a58d3b0a58904e8e1b00e0e13a1f48e710..c567278f70ea79740510aea4099170179f7e1f85 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -24,7 +24,7 @@ sc-tracing = { path = "../../../substrate/client/tracing" } codec = { package = "parity-scale-codec", version = "3.6.1" } primitives = { package = "polkadot-primitives", path = "../../primitives" } bs58 = { version = "0.5.0", features = ["alloc"] } -log = "0.4.17" +log = { workspace = true, default-features = true } [dev-dependencies] assert_cmd = "2.0.4" diff --git a/polkadot/node/network/approval-distribution/Cargo.toml b/polkadot/node/network/approval-distribution/Cargo.toml index a1fa803abc25fcbe587c4c574902ca8dbf63ad4b..2bc09c5f42acad52a8a96dab3916ed6314f27d56 100644 --- a/polkadot/node/network/approval-distribution/Cargo.toml +++ b/polkadot/node/network/approval-distribution/Cargo.toml @@ -38,4 +38,4 @@ schnorrkel = { version = "0.11.4", default-features = false } rand_core = "0.6.2" rand_chacha = "0.3.1" env_logger = "0.9.0" -log = "0.4.17" +log = { workspace = true, default-features = true } diff --git a/polkadot/node/network/availability-distribution/Cargo.toml b/polkadot/node/network/availability-distribution/Cargo.toml index a1484429ed633a09974e230bcf5be118f7280e15..432501ed23fbbca5e0ea49c82b8ac19efa5bd473 100644 --- a/polkadot/node/network/availability-distribution/Cargo.toml +++ b/polkadot/node/network/availability-distribution/Cargo.toml @@ -21,7 +21,7 @@ polkadot-node-subsystem-util = { path = "../../subsystem-util" } polkadot-node-primitives = { path = "../../primitives" } sp-core = { path = "../../../../substrate/primitives/core", features = ["std"] } sp-keystore = { path = "../../../../substrate/primitives/keystore" } -thiserror = "1.0.48" +thiserror = { workspace = true } rand = "0.8.5" derive_more = "0.99.17" schnellru = "0.2.1" diff --git a/polkadot/node/network/availability-recovery/Cargo.toml b/polkadot/node/network/availability-recovery/Cargo.toml index 9f1d9052312f2cbbff8f32ffc22b7e1c234cfc3f..9eddf5c86d2ed905888cf5e5fad4dd200fdbee08 100644 --- a/polkadot/node/network/availability-recovery/Cargo.toml +++ b/polkadot/node/network/availability-recovery/Cargo.toml @@ -15,7 +15,7 @@ tokio = "1.24.2" schnellru = "0.2.1" rand = "0.8.5" fatality = "0.0.6" -thiserror = "1.0.48" +thiserror = { workspace = true } async-trait = "0.1.74" gum = { package = "tracing-gum", path = "../../gum" } @@ -32,7 +32,7 @@ sc-network = { path = "../../../../substrate/client/network" } assert_matches = "1.4.0" env_logger = "0.9.0" futures-timer = "3.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } sp-core = { path = "../../../../substrate/primitives/core" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } diff --git a/polkadot/node/network/bitfield-distribution/Cargo.toml b/polkadot/node/network/bitfield-distribution/Cargo.toml index 8a05bcbd493fd21c5c55bd1603319465f6003ae8..0ddb5f643b89d382855de92fe450a5807e6e21ac 100644 --- a/polkadot/node/network/bitfield-distribution/Cargo.toml +++ b/polkadot/node/network/bitfield-distribution/Cargo.toml @@ -29,7 +29,7 @@ sp-authority-discovery = { path = "../../../../substrate/primitives/authority-di sp-keystore = { path = "../../../../substrate/primitives/keystore" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } maplit = "1.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" assert_matches = "1.4.0" rand_chacha = "0.3.1" diff --git a/polkadot/node/network/bridge/Cargo.toml b/polkadot/node/network/bridge/Cargo.toml index 0cdd7bfbcb136bef97f856dc5fd3cb0a3cdf507e..2e889fc30eb90063f07c05e34e51d863a3d32f59 100644 --- a/polkadot/node/network/bridge/Cargo.toml +++ b/polkadot/node/network/bridge/Cargo.toml @@ -25,7 +25,7 @@ polkadot-overseer = { path = "../../overseer" } parking_lot = "0.12.1" bytes = "1" fatality = "0.0.6" -thiserror = "1" +thiserror = { workspace = true } [dev-dependencies] assert_matches = "1.4.0" diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index 9a9ce6fce6359a9e44e7820839952c5f9a8c11ba..f0f8be0f7bab240a928be9f30a18ca10b14eb16d 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -25,11 +25,11 @@ polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } polkadot-node-subsystem = { path = "../../subsystem" } fatality = "0.0.6" -thiserror = "1.0.48" +thiserror = { workspace = true } tokio-util = "0.7.1" [dev-dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" assert_matches = "1.4.0" diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml index 319b743aa586dfb5104eb2b1fe9f3aa042c2c13f..14d59d04f2bff7b838717edb927401035537ff4d 100644 --- a/polkadot/node/network/dispute-distribution/Cargo.toml +++ b/polkadot/node/network/dispute-distribution/Cargo.toml @@ -24,7 +24,7 @@ polkadot-node-primitives = { path = "../../primitives" } sc-network = { path = "../../../../substrate/client/network" } sp-application-crypto = { path = "../../../../substrate/primitives/application-crypto" } sp-keystore = { path = "../../../../substrate/primitives/keystore" } -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" schnellru = "0.2.1" indexmap = "2.0.0" diff --git a/polkadot/node/network/gossip-support/Cargo.toml b/polkadot/node/network/gossip-support/Cargo.toml index c17f39b019de1f6f248236c387742a9033e60df8..8d0edc206d728b16e0448c3339c1719f26d81413 100644 --- a/polkadot/node/network/gossip-support/Cargo.toml +++ b/polkadot/node/network/gossip-support/Cargo.toml @@ -38,5 +38,6 @@ polkadot-node-subsystem-test-helpers = { path = "../../subsystem-test-helpers" } assert_matches = "1.4.0" async-trait = "0.1.74" +parking_lot = "0.12.1" lazy_static = "1.4.0" quickcheck = "1.0.3" diff --git a/polkadot/node/network/gossip-support/src/lib.rs b/polkadot/node/network/gossip-support/src/lib.rs index e9cb8a4de1c47b5e3f6e285f86ad38b3cf5ac342..4dfdd1f7208f66ec4a787ffe8bc7f72c41575c9d 100644 --- a/polkadot/node/network/gossip-support/src/lib.rs +++ b/polkadot/node/network/gossip-support/src/lib.rs @@ -63,8 +63,12 @@ use metrics::Metrics; const LOG_TARGET: &str = "parachain::gossip-support"; // How much time should we wait to reissue a connection request // since the last authority discovery resolution failure. +#[cfg(not(test))] const BACKOFF_DURATION: Duration = Duration::from_secs(5); +#[cfg(test)] +const BACKOFF_DURATION: Duration = Duration::from_millis(500); + /// Duration after which we consider low connectivity a problem. /// /// Especially at startup low connectivity is expected (authority discovery cache needs to be @@ -271,8 +275,8 @@ where ) .await?; } - // authority_discovery is just a cache so let's try every leaf to detect if there - // are new authorities there. + // authority_discovery is just a cache so let's try every time we try to re-connect + // if new authorities are present. self.update_authority_ids(sender, session_info.discovery_keys).await; } } diff --git a/polkadot/node/network/gossip-support/src/tests.rs b/polkadot/node/network/gossip-support/src/tests.rs index e5ee101c31d857b2dbd540596649ddaf9b826bd5..6817c85f98d87c03b3c252783c464efed88dfdb6 100644 --- a/polkadot/node/network/gossip-support/src/tests.rs +++ b/polkadot/node/network/gossip-support/src/tests.rs @@ -25,13 +25,19 @@ use lazy_static::lazy_static; use quickcheck::quickcheck; use rand::seq::SliceRandom as _; +use parking_lot::Mutex; use sc_network::multiaddr::Protocol; use sp_authority_discovery::AuthorityPair as AuthorityDiscoveryPair; use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch}; use sp_core::crypto::Pair as PairT; use sp_keyring::Sr25519Keyring; +use std::sync::Arc; -use polkadot_node_network_protocol::grid_topology::{SessionGridTopology, TopologyPeerInfo}; +use polkadot_node_network_protocol::{ + grid_topology::{SessionGridTopology, TopologyPeerInfo}, + peer_set::ValidationVersion, + ObservedRole, +}; use polkadot_node_subsystem::messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest}; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::TimeoutExt as _; @@ -51,7 +57,6 @@ const AUTHORITY_KEYRINGS: &[Sr25519Keyring] = &[ ]; lazy_static! { - static ref MOCK_AUTHORITY_DISCOVERY: MockAuthorityDiscovery = MockAuthorityDiscovery::new(); static ref AUTHORITIES: Vec = AUTHORITY_KEYRINGS.iter().map(|k| k.public().into()).collect(); @@ -89,17 +94,14 @@ type VirtualOverseer = test_helpers::TestSubsystemContextHandle>, - authorities: HashMap>, + addrs: Arc>>>, + authorities: Arc>>>, } impl MockAuthorityDiscovery { - fn new() -> Self { - let authorities: HashMap<_, _> = PAST_PRESENT_FUTURE_AUTHORITIES - .clone() - .into_iter() - .map(|a| (PeerId::random(), a)) - .collect(); + fn new(authorities: Vec) -> Self { + let authorities: HashMap<_, _> = + authorities.clone().into_iter().map(|a| (PeerId::random(), a)).collect(); let addrs = authorities .clone() .into_iter() @@ -109,10 +111,37 @@ impl MockAuthorityDiscovery { }) .collect(); Self { - addrs, - authorities: authorities.into_iter().map(|(p, a)| (p, HashSet::from([a]))).collect(), + addrs: Arc::new(Mutex::new(addrs)), + authorities: Arc::new(Mutex::new( + authorities.into_iter().map(|(p, a)| (p, HashSet::from([a]))).collect(), + )), } } + + fn authorities(&self) -> HashMap> { + self.authorities.lock().clone() + } + + fn add_more_authorties( + &self, + new_known: Vec, + ) -> HashMap> { + let authorities: HashMap<_, _> = + new_known.clone().into_iter().map(|a| (PeerId::random(), a)).collect(); + let addrs: HashMap> = authorities + .clone() + .into_iter() + .map(|(p, a)| { + let multiaddr = Multiaddr::empty().with(Protocol::P2p(p.into())); + (a, HashSet::from([multiaddr])) + }) + .collect(); + let authorities: HashMap> = + authorities.into_iter().map(|(p, a)| (p, HashSet::from([a]))).collect(); + self.addrs.as_ref().lock().extend(addrs); + self.authorities.as_ref().lock().extend(authorities.clone()); + authorities + } } #[async_trait] @@ -121,19 +150,23 @@ impl AuthorityDiscovery for MockAuthorityDiscovery { &mut self, authority: polkadot_primitives::AuthorityDiscoveryId, ) -> Option> { - self.addrs.get(&authority).cloned() + self.addrs.lock().get(&authority).cloned() } + async fn get_authority_ids_by_peer_id( &mut self, peer_id: polkadot_node_network_protocol::PeerId, ) -> Option> { - self.authorities.get(&peer_id).cloned() + self.authorities.as_ref().lock().get(&peer_id).cloned() } } -async fn get_multiaddrs(authorities: Vec) -> Vec> { +async fn get_multiaddrs( + authorities: Vec, + mock_authority_discovery: MockAuthorityDiscovery, +) -> Vec> { let mut addrs = Vec::with_capacity(authorities.len()); - let mut discovery = MOCK_AUTHORITY_DISCOVERY.clone(); + let mut discovery = mock_authority_discovery.clone(); for authority in authorities.into_iter() { if let Some(addr) = discovery.get_addresses_by_authority_id(authority).await { addrs.push(addr); @@ -144,9 +177,10 @@ async fn get_multiaddrs(authorities: Vec) -> Vec, + mock_authority_discovery: MockAuthorityDiscovery, ) -> HashMap> { let mut addrs = HashMap::with_capacity(authorities.len()); - let mut discovery = MOCK_AUTHORITY_DISCOVERY.clone(); + let mut discovery = mock_authority_discovery.clone(); for authority in authorities.into_iter() { if let Some(addr) = discovery.get_addresses_by_authority_id(authority.clone()).await { addrs.insert(authority, addr); @@ -155,12 +189,10 @@ async fn get_address_map( addrs } -fn make_subsystem() -> GossipSupport { - GossipSupport::new( - make_ferdie_keystore(), - MOCK_AUTHORITY_DISCOVERY.clone(), - Metrics::new_dummy(), - ) +fn make_subsystem_with_authority_discovery( + mock: MockAuthorityDiscovery, +) -> GossipSupport { + GossipSupport::new(make_ferdie_keystore(), mock, Metrics::new_dummy()) } fn test_harness, AD: AuthorityDiscovery>( @@ -291,59 +323,65 @@ async fn test_neighbors(overseer: &mut VirtualOverseer, expected_session: Sessio #[test] fn issues_a_connection_request_on_new_session() { + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); + let mock_authority_discovery_clone = mock_authority_discovery.clone(); let hash = Hash::repeat_byte(0xAA); - let state = test_harness(make_subsystem(), |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - overseer_signal_active_leaves(overseer, hash).await; - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(1)).unwrap(); - } - ); + let state = test_harness( + make_subsystem_with_authority_discovery(mock_authority_discovery.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(s, tx), - )) => { - assert_eq!(relay_parent, hash); - assert_eq!(s, 1); - tx.send(Ok(Some(make_session_info()))).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + tx.send(Ok(Some(make_session_info()))).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::Authorities(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(AUTHORITIES.clone())).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(AUTHORITIES.clone())).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { - validator_addrs, - peer_set, - }) => { - assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone()).await); - assert_eq!(peer_set, PeerSet::Validation); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery_clone).await); + assert_eq!(peer_set, PeerSet::Validation); + } + ); - test_neighbors(overseer, 1).await; + test_neighbors(overseer, 1).await; - virtual_overseer - }); + virtual_overseer + }, + ); assert_eq!(state.last_session_index, Some(1)); assert!(state.last_failure.is_none()); @@ -363,6 +401,7 @@ fn issues_a_connection_request_on_new_session() { tx.send(Ok(1)).unwrap(); } ); + virtual_overseer }); @@ -414,7 +453,7 @@ fn issues_a_connection_request_on_new_session() { validator_addrs, peer_set, }) => { - assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone()).await); + assert_eq!(validator_addrs, get_multiaddrs(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery.clone()).await); assert_eq!(peer_set, PeerSet::Validation); } ); @@ -430,125 +469,405 @@ fn issues_a_connection_request_on_new_session() { #[test] fn issues_connection_request_to_past_present_future() { let hash = Hash::repeat_byte(0xAA); - test_harness(make_subsystem(), |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - overseer_signal_active_leaves(overseer, hash).await; - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(1)).unwrap(); + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); + test_harness( + make_subsystem_with_authority_discovery(mock_authority_discovery.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + tx.send(Ok(Some(make_session_info()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + let all_without_ferdie: Vec<_> = PAST_PRESENT_FUTURE_AUTHORITIES + .iter() + .cloned() + .filter(|p| p != &Sr25519Keyring::Ferdie.public().into()) + .collect(); + + let addrs = get_multiaddrs(all_without_ferdie, mock_authority_discovery.clone()).await; + + assert_eq!(validator_addrs, addrs); + assert_eq!(peer_set, PeerSet::Validation); + } + ); + + // Ensure neighbors are unaffected + test_neighbors(overseer, 1).await; + + virtual_overseer + }, + ); +} + +// Test we notify peer about learning of the authority ID after session boundary, when we couldn't +// connect to more than 1/3 of the authorities. +#[test] +fn issues_update_authorities_after_session() { + let hash = Hash::repeat_byte(0xAA); + + let mut authorities = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + let unknown_at_session = authorities.split_off(authorities.len() / 3 - 1); + let mut authority_discovery_mock = MockAuthorityDiscovery::new(authorities); + + test_harness( + make_subsystem_with_authority_discovery(authority_discovery_mock.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + // 1. Initialize with the first leaf in the session. + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut session_info = make_session_info(); + session_info.discovery_keys = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + tx.send(Ok(Some(session_info))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + let all_without_ferdie: Vec<_> = PAST_PRESENT_FUTURE_AUTHORITIES + .iter() + .cloned() + .filter(|p| p != &Sr25519Keyring::Ferdie.public().into()) + .collect(); + + let addrs = get_multiaddrs(all_without_ferdie, authority_discovery_mock.clone()).await; + + assert_eq!(validator_addrs, addrs); + assert_eq!(peer_set, PeerSet::Validation); + } + ); + + // Ensure neighbors are unaffected + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _, + RuntimeApiRequest::CurrentBabeEpoch(tx), + )) => { + let _ = tx.send(Ok(BabeEpoch { + epoch_index: 2 as _, + start_slot: 0.into(), + duration: 200, + authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)], + randomness: [0u8; 32], + config: BabeEpochConfiguration { + c: (1, 4), + allowed_slots: AllowedSlots::PrimarySlots, + }, + })).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeRx(NetworkBridgeRxMessage::NewGossipTopology { + session: _, + local_index: _, + canonical_shuffling: _, + shuffled_indices: _, + }) => { + + } + ); + + // 2. Connect all authorities that are known so far. + let known_authorities = authority_discovery_mock.authorities(); + for (peer_id, _id) in known_authorities.iter() { + let msg = + GossipSupportMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( + *peer_id, + ObservedRole::Authority, + ValidationVersion::V3.into(), + None, + )); + overseer.send(FromOrchestra::Communication { msg }).await } - ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(s, tx), - )) => { - assert_eq!(relay_parent, hash); - assert_eq!(s, 1); - tx.send(Ok(Some(make_session_info()))).unwrap(); + Delay::new(BACKOFF_DURATION).await; + // 3. Send a new leaf after BACKOFF_DURATION and check UpdateAuthority is emitted for + // all known connected peers. + let hash = Hash::repeat_byte(0xBB); + overseer_signal_active_leaves(overseer, hash).await; + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut session_info = make_session_info(); + session_info.discovery_keys = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + tx.send(Ok(Some(session_info))).unwrap(); + + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); + + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs: _, + peer_set: _, + }) => { + } + ); + + for _ in 0..known_authorities.len() { + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeRx(NetworkBridgeRxMessage::UpdatedAuthorityIds { + peer_id, + authority_ids, + }) => { + assert_eq!(authority_discovery_mock.get_authority_ids_by_peer_id(peer_id).await.unwrap_or_default(), authority_ids); + } + ); } - ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::Authorities(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + assert!(overseer.recv().timeout(TIMEOUT).await.is_none()); + // 4. Connect more authorities except one + let newly_added = authority_discovery_mock.add_more_authorties(unknown_at_session); + let mut newly_added_iter = newly_added.iter(); + let unconnected_at_last_retry = newly_added_iter + .next() + .map(|(peer_id, authority_id)| (*peer_id, authority_id.clone())) + .unwrap(); + for (peer_id, _) in newly_added_iter { + let msg = + GossipSupportMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( + *peer_id, + ObservedRole::Authority, + ValidationVersion::V3.into(), + None, + )); + overseer.send(FromOrchestra::Communication { msg }).await } - ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { - validator_addrs, - peer_set, - }) => { - let all_without_ferdie: Vec<_> = PAST_PRESENT_FUTURE_AUTHORITIES - .iter() - .cloned() - .filter(|p| p != &Sr25519Keyring::Ferdie.public().into()) - .collect(); + // 5. Send a new leaf and check UpdateAuthority is emitted only for the newly connected + // peers. + let hash = Hash::repeat_byte(0xCC); + Delay::new(BACKOFF_DURATION).await; + overseer_signal_active_leaves(overseer, hash).await; - let addrs = get_multiaddrs(all_without_ferdie).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); - assert_eq!(validator_addrs, addrs); - assert_eq!(peer_set, PeerSet::Validation); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut session_info = make_session_info(); + session_info.discovery_keys = PAST_PRESENT_FUTURE_AUTHORITIES.clone(); + tx.send(Ok(Some(session_info))).unwrap(); + } + ); - // Ensure neighbors are unaffected - test_neighbors(overseer, 1).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(PAST_PRESENT_FUTURE_AUTHORITIES.clone())).unwrap(); + } + ); - virtual_overseer - }); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs: _, + peer_set: _, + }) => { + } + ); + + for _ in 1..newly_added.len() { + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeRx(NetworkBridgeRxMessage::UpdatedAuthorityIds { + peer_id, + authority_ids, + }) => { + assert_ne!(peer_id, unconnected_at_last_retry.0); + assert_eq!(newly_added.get(&peer_id).cloned().unwrap_or_default(), authority_ids); + } + ); + } + + assert!(overseer.recv().timeout(TIMEOUT).await.is_none()); + virtual_overseer + }, + ); } #[test] fn disconnect_when_not_in_past_present_future() { sp_tracing::try_init_simple(); + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); let hash = Hash::repeat_byte(0xAA); - test_harness(make_subsystem(), |mut virtual_overseer| async move { - let overseer = &mut virtual_overseer; - overseer_signal_active_leaves(overseer, hash).await; - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionIndexForChild(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(1)).unwrap(); - } - ); + test_harness( + make_subsystem_with_authority_discovery(mock_authority_discovery.clone()), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + overseer_signal_active_leaves(overseer, hash).await; + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(1)).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::SessionInfo(s, tx), - )) => { - assert_eq!(relay_parent, hash); - assert_eq!(s, 1); - let mut heute_leider_nicht = make_session_info(); - heute_leider_nicht.discovery_keys = AUTHORITIES_WITHOUT_US.clone(); - tx.send(Ok(Some(heute_leider_nicht))).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(s, tx), + )) => { + assert_eq!(relay_parent, hash); + assert_eq!(s, 1); + let mut heute_leider_nicht = make_session_info(); + heute_leider_nicht.discovery_keys = AUTHORITIES_WITHOUT_US.clone(); + tx.send(Ok(Some(heute_leider_nicht))).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::Authorities(tx), - )) => { - assert_eq!(relay_parent, hash); - tx.send(Ok(AUTHORITIES_WITHOUT_US.clone())).unwrap(); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::Authorities(tx), + )) => { + assert_eq!(relay_parent, hash); + tx.send(Ok(AUTHORITIES_WITHOUT_US.clone())).unwrap(); + } + ); - assert_matches!( - overseer_recv(overseer).await, - AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { - validator_addrs, - peer_set, - }) => { - assert!(validator_addrs.is_empty()); - assert_eq!(peer_set, PeerSet::Validation); - } - ); + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ConnectToResolvedValidators { + validator_addrs, + peer_set, + }) => { + assert!(validator_addrs.is_empty()); + assert_eq!(peer_set, PeerSet::Validation); + } + ); - virtual_overseer - }); + virtual_overseer + }, + ); } #[test] @@ -579,13 +898,15 @@ fn test_log_output() { #[test] fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { let hash = Hash::repeat_byte(0xAA); - let mut state = make_subsystem(); + let mock_authority_discovery = + MockAuthorityDiscovery::new(PAST_PRESENT_FUTURE_AUTHORITIES.clone()); + let state = make_subsystem_with_authority_discovery(mock_authority_discovery.clone()); // There will be two lookup failures: let alice = Sr25519Keyring::Alice.public().into(); let bob = Sr25519Keyring::Bob.public().into(); - let alice_addr = state.authority_discovery.addrs.remove(&alice); - state.authority_discovery.addrs.remove(&bob); - + let alice_addr = state.authority_discovery.addrs.lock().remove(&alice); + state.authority_discovery.addrs.lock().remove(&bob); + let mock_authority_discovery_clone = mock_authority_discovery.clone(); let mut state = { let alice = alice.clone(); let bob = bob.clone(); @@ -633,7 +954,7 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { validator_addrs, peer_set, }) => { - let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone()).await; + let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery_clone.clone()).await; expected.remove(&alice); expected.remove(&bob); let expected: HashSet = expected.into_values().flat_map(|v| v.into_iter()).collect(); @@ -652,7 +973,7 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { assert!(state.last_failure.is_some()); state.last_failure = state.last_failure.and_then(|i| i.checked_sub(BACKOFF_DURATION)); // One error less: - state.authority_discovery.addrs.insert(alice, alice_addr.unwrap()); + state.authority_discovery.addrs.lock().insert(alice, alice_addr.unwrap()); let hash = Hash::repeat_byte(0xBB); let state = test_harness(state, |mut virtual_overseer| async move { @@ -698,7 +1019,7 @@ fn issues_a_connection_request_when_last_request_was_mostly_unresolved() { validator_addrs, peer_set, }) => { - let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone()).await; + let mut expected = get_address_map(AUTHORITIES_WITHOUT_US.clone(), mock_authority_discovery.clone()).await; expected.remove(&bob); let expected: HashSet = expected.into_values().flat_map(|v| v.into_iter()).collect(); assert_eq!(validator_addrs.into_iter().flat_map(|v| v.into_iter()).collect::>(), expected); diff --git a/polkadot/node/network/protocol/Cargo.toml b/polkadot/node/network/protocol/Cargo.toml index a8b0cf2737eaa12ed3633aac8588ed6f19407ebf..7efa0b8ca820d651d77a53a95d61e7672c5f9737 100644 --- a/polkadot/node/network/protocol/Cargo.toml +++ b/polkadot/node/network/protocol/Cargo.toml @@ -21,7 +21,7 @@ sc-network = { path = "../../../../substrate/client/network" } sc-authority-discovery = { path = "../../../../substrate/client/authority-discovery" } strum = { version = "0.24", features = ["derive"] } futures = "0.3.21" -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" rand = "0.8" derive_more = "0.99" diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml index b07d84390dcdd1e5413889c4749f187ce1ddf1ea..01f7d818c1c52ffd677379e3a0a9d2f543572d92 100644 --- a/polkadot/node/network/statement-distribution/Cargo.toml +++ b/polkadot/node/network/statement-distribution/Cargo.toml @@ -23,7 +23,7 @@ polkadot-node-network-protocol = { path = "../protocol" } arrayvec = "0.7.4" indexmap = "2.0.0" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" bitvec = "1" diff --git a/polkadot/node/network/statement-distribution/src/v2/cluster.rs b/polkadot/node/network/statement-distribution/src/v2/cluster.rs index 619114de9670c8aef50ce129b6c26bf6ff206f70..c09916e56201f20a0fbbbac349681b512f1fd5fe 100644 --- a/polkadot/node/network/statement-distribution/src/v2/cluster.rs +++ b/polkadot/node/network/statement-distribution/src/v2/cluster.rs @@ -55,8 +55,9 @@ //! and to keep track of what we have sent to other validators in the group and what we may //! continue to send them. -use polkadot_primitives::{CandidateHash, CompactStatement, ValidatorIndex}; +use polkadot_primitives::{CandidateHash, CompactStatement, Hash, ValidatorIndex}; +use crate::LOG_TARGET; use std::collections::{HashMap, HashSet}; #[derive(Hash, PartialEq, Eq)] @@ -424,6 +425,28 @@ impl ClusterTracker { fn is_in_group(&self, validator: ValidatorIndex) -> bool { self.validators.contains(&validator) } + + /// Dumps pending statement for this cluster. + /// + /// Normally we should not have pending statements to validators in our cluster, + /// but if we do for all validators in our cluster, then we don't participate + /// in backing. Ocasional pending statements are expected if two authorities + /// can't detect each otehr or after restart, where it takes a while to discover + /// the whole network. + + pub fn warn_if_too_many_pending_statements(&self, parent_hash: Hash) { + if self.pending.iter().filter(|pending| !pending.1.is_empty()).count() >= + self.validators.len() + { + gum::warn!( + target: LOG_TARGET, + pending_statements = ?self.pending, + ?parent_hash, + "Cluster has too many pending statements, something wrong with our connection to our group peers \n + Restart might be needed if validator gets 0 backing rewards for more than 3-4 consecutive sessions" + ); + } + } } /// Incoming statement was accepted. diff --git a/polkadot/node/network/statement-distribution/src/v2/grid.rs b/polkadot/node/network/statement-distribution/src/v2/grid.rs index 19f23053192c12a7e3bcb3ae73dbbd972e1c619d..24d846c840e00c49446a0525b3768353f56ae524 100644 --- a/polkadot/node/network/statement-distribution/src/v2/grid.rs +++ b/polkadot/node/network/statement-distribution/src/v2/grid.rs @@ -527,12 +527,16 @@ impl GridTracker { } /// Determine the validators which can send a statement to us by direct broadcast. + /// + /// Returns a list of tuples representing each potential sender(ValidatorIndex) + /// and if the sender should already know about the statement, because we just + /// sent it to it. pub fn direct_statement_providers( &self, groups: &Groups, originator: ValidatorIndex, statement: &CompactStatement, - ) -> Vec { + ) -> Vec<(ValidatorIndex, bool)> { let (g, c_h, kind, in_group) = match extract_statement_and_group_info(groups, originator, statement) { None => return Vec::new(), @@ -616,12 +620,13 @@ impl GridTracker { originator: ValidatorIndex, counterparty: ValidatorIndex, statement: &CompactStatement, + received: bool, ) { if let Some((_, c_h, kind, in_group)) = extract_statement_and_group_info(groups, originator, statement) { if let Some(known) = self.confirmed_backed.get_mut(&c_h) { - known.sent_or_received_direct_statement(counterparty, in_group, kind); + known.sent_or_received_direct_statement(counterparty, in_group, kind, received); if let Some(pending) = self.pending_statements.get_mut(&counterparty) { pending.remove(&(originator, statement.clone())); @@ -908,6 +913,12 @@ struct MutualKnowledge { /// `Some` only if we have advertised, acknowledged, or requested the candidate /// from them. local_knowledge: Option, + /// Knowledge peer circulated to us, this is different from `local_knowledge` and + /// `remote_knowledge`, through the fact that includes only statements that we received from + /// peer while the other two, after manifest exchange part will include both what we sent to + /// the peer and what we received from peer, see `sent_or_received_direct_statement` for more + /// details. + received_knowledge: Option, } // A utility struct for keeping track of metadata about candidates @@ -933,10 +944,13 @@ impl KnownBackedCandidate { } fn manifest_sent_to(&mut self, validator: ValidatorIndex, local_knowledge: StatementFilter) { - let k = self - .mutual_knowledge - .entry(validator) - .or_insert_with(|| MutualKnowledge { remote_knowledge: None, local_knowledge: None }); + let k = self.mutual_knowledge.entry(validator).or_insert_with(|| MutualKnowledge { + remote_knowledge: None, + local_knowledge: None, + received_knowledge: None, + }); + k.received_knowledge = + Some(StatementFilter::blank(local_knowledge.seconded_in_group.len())); k.local_knowledge = Some(local_knowledge); } @@ -946,20 +960,24 @@ impl KnownBackedCandidate { validator: ValidatorIndex, remote_knowledge: StatementFilter, ) { - let k = self - .mutual_knowledge - .entry(validator) - .or_insert_with(|| MutualKnowledge { remote_knowledge: None, local_knowledge: None }); + let k = self.mutual_knowledge.entry(validator).or_insert_with(|| MutualKnowledge { + remote_knowledge: None, + local_knowledge: None, + received_knowledge: None, + }); k.remote_knowledge = Some(remote_knowledge); } + /// Returns a list of tuples representing each potential sender(ValidatorIndex) + /// and if the sender should already know about the statement, because we just + /// sent it to it. fn direct_statement_senders( &self, group_index: GroupIndex, originator_index_in_group: usize, statement_kind: StatementKind, - ) -> Vec { + ) -> Vec<(ValidatorIndex, bool)> { if group_index != self.group_index { return Vec::new() } @@ -968,11 +986,18 @@ impl KnownBackedCandidate { .iter() .filter(|(_, k)| k.remote_knowledge.is_some()) .filter(|(_, k)| { - k.local_knowledge + k.received_knowledge .as_ref() .map_or(false, |r| !r.contains(originator_index_in_group, statement_kind)) }) - .map(|(v, _)| *v) + .map(|(v, k)| { + ( + *v, + k.local_knowledge + .as_ref() + .map_or(false, |r| r.contains(originator_index_in_group, statement_kind)), + ) + }) .collect() } @@ -1014,12 +1039,19 @@ impl KnownBackedCandidate { validator: ValidatorIndex, statement_index_in_group: usize, statement_kind: StatementKind, + received: bool, ) { if let Some(k) = self.mutual_knowledge.get_mut(&validator) { if let (Some(r), Some(l)) = (k.remote_knowledge.as_mut(), k.local_knowledge.as_mut()) { r.set(statement_index_in_group, statement_kind); l.set(statement_index_in_group, statement_kind); } + + if received { + k.received_knowledge + .as_mut() + .map(|knowledge| knowledge.set(statement_index_in_group, statement_kind)); + } } } @@ -2238,6 +2270,7 @@ mod tests { validator_index, counterparty, &statement, + false, ); // There should be no pending statements now (for the counterparty). diff --git a/polkadot/node/network/statement-distribution/src/v2/mod.rs b/polkadot/node/network/statement-distribution/src/v2/mod.rs index 52852657870db30c6851c563e1c9516b156c192e..2c9cdba4ea8eb1155f9307eb01460a777ae9a737 100644 --- a/polkadot/node/network/statement-distribution/src/v2/mod.rs +++ b/polkadot/node/network/statement-distribution/src/v2/mod.rs @@ -93,7 +93,19 @@ mod statement_store; #[cfg(test)] mod tests; -const COST_UNEXPECTED_STATEMENT: Rep = Rep::CostMinor("Unexpected Statement"); +const COST_UNEXPECTED_STATEMENT_NOT_VALIDATOR: Rep = + Rep::CostMinor("Unexpected Statement, not a validator"); +const COST_UNEXPECTED_STATEMENT_VALIDATOR_NOT_FOUND: Rep = + Rep::CostMinor("Unexpected Statement, validator not found"); +const COST_UNEXPECTED_STATEMENT_INVALID_SENDER: Rep = + Rep::CostMinor("Unexpected Statement, invalid sender"); +const COST_UNEXPECTED_STATEMENT_BAD_ADVERTISE: Rep = + Rep::CostMinor("Unexpected Statement, bad advertise"); +const COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED: Rep = + Rep::CostMinor("Unexpected Statement, cluster rejected"); +const COST_UNEXPECTED_STATEMENT_NOT_IN_GROUP: Rep = + Rep::CostMinor("Unexpected Statement, not in group"); + const COST_UNEXPECTED_STATEMENT_MISSING_KNOWLEDGE: Rep = Rep::CostMinor("Unexpected Statement, missing knowledge for relay parent"); const COST_EXCESSIVE_SECONDED: Rep = Rep::CostMinor("Sent Excessive `Seconded` Statements"); @@ -239,6 +251,13 @@ impl PerSessionState { if local_index.is_some() { self.local_validator.get_or_insert(LocalValidatorIndex::Inactive); } + + gum::info!( + target: LOG_TARGET, + index_in_gossip_topology = ?local_index, + index_in_parachain_authorities = ?self.local_validator, + "Node uses the following topology indices" + ); } /// Returns `true` if local is neither active or inactive validator node. @@ -756,7 +775,15 @@ pub(crate) fn handle_deactivate_leaves(state: &mut State, leaves: &[Hash]) { let pruned = state.implicit_view.deactivate_leaf(*leaf); for pruned_rp in pruned { // clean up per-relay-parent data based on everything removed. - state.per_relay_parent.remove(&pruned_rp); + state + .per_relay_parent + .remove(&pruned_rp) + .as_ref() + .and_then(|pruned| pruned.active_validator_state()) + .map(|active_state| { + active_state.cluster_tracker.warn_if_too_many_pending_statements(pruned_rp) + }); + // clean up requests related to this relay parent. state.request_manager.remove_by_relay_parent(*leaf); } @@ -1086,6 +1113,7 @@ async fn send_pending_grid_messages( originator, peer_validator_id, &compact, + false, ); } @@ -1378,6 +1406,7 @@ async fn circulate_statement( originator, target, &compact_statement, + false, ); }, } @@ -1515,7 +1544,13 @@ async fn handle_incoming_statement( // we shouldn't be receiving statements unless we're a validator // this session. if per_session.is_not_validator() { - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_NOT_VALIDATOR, + ) + .await; } return }, @@ -1526,7 +1561,13 @@ async fn handle_incoming_statement( match per_session.groups.by_validator_index(statement.unchecked_validator_index()) { Some(g) => g, None => { - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_VALIDATOR_NOT_FOUND, + ) + .await; return }, }; @@ -1565,38 +1606,45 @@ async fn handle_incoming_statement( (active, idx) }; - let checked_statement = - if let Some((active, cluster_sender_index)) = active.zip(cluster_sender_index) { - match handle_cluster_statement( - relay_parent, - &mut active.cluster_tracker, - per_relay_parent.session, - &per_session.session_info, - statement, - cluster_sender_index, - ) { - Ok(Some(s)) => s, - Ok(None) => return, - Err(rep) => { - modify_reputation(reputation, ctx.sender(), peer, rep).await; - return - }, - } - } else { - let grid_sender_index = local_validator - .grid_tracker - .direct_statement_providers( - &per_session.groups, - statement.unchecked_validator_index(), - statement.unchecked_payload(), - ) - .into_iter() - .filter_map(|i| session_info.discovery_keys.get(i.0 as usize).map(|ad| (i, ad))) - .filter(|(_, ad)| peer_state.is_authority(ad)) - .map(|(i, _)| i) - .next(); + let checked_statement = if let Some((active, cluster_sender_index)) = + active.zip(cluster_sender_index) + { + match handle_cluster_statement( + relay_parent, + &mut active.cluster_tracker, + per_relay_parent.session, + &per_session.session_info, + statement, + cluster_sender_index, + ) { + Ok(Some(s)) => s, + Ok(None) => return, + Err(rep) => { + modify_reputation(reputation, ctx.sender(), peer, rep).await; + return + }, + } + } else { + let grid_sender_index = local_validator + .grid_tracker + .direct_statement_providers( + &per_session.groups, + statement.unchecked_validator_index(), + statement.unchecked_payload(), + ) + .into_iter() + .filter_map(|(i, validator_knows_statement)| { + session_info + .discovery_keys + .get(i.0 as usize) + .map(|ad| (i, ad, validator_knows_statement)) + }) + .filter(|(_, ad, _)| peer_state.is_authority(ad)) + .map(|(i, _, validator_knows_statement)| (i, validator_knows_statement)) + .next(); - if let Some(grid_sender_index) = grid_sender_index { + if let Some((grid_sender_index, validator_knows_statement)) = grid_sender_index { + if !validator_knows_statement { match handle_grid_statement( relay_parent, &mut local_validator.grid_tracker, @@ -1612,11 +1660,22 @@ async fn handle_incoming_statement( }, } } else { - // Not a cluster or grid peer. - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; - return + // Reward the peer for sending us the statement + modify_reputation(reputation, ctx.sender(), peer, BENEFIT_VALID_STATEMENT).await; + return; } - }; + } else { + // Not a cluster or grid peer. + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_INVALID_SENDER, + ) + .await; + return + } + }; let statement = checked_statement.payload().clone(); let originator_index = checked_statement.validator_index(); @@ -1635,7 +1694,13 @@ async fn handle_incoming_statement( ); if let Err(BadAdvertisement) = res { - modify_reputation(reputation, ctx.sender(), peer, COST_UNEXPECTED_STATEMENT).await; + modify_reputation( + reputation, + ctx.sender(), + peer, + COST_UNEXPECTED_STATEMENT_BAD_ADVERTISE, + ) + .await; return } } @@ -1743,11 +1808,11 @@ fn handle_cluster_statement( Ok(ClusterAccept::WithPrejudice) => false, Err(ClusterRejectIncoming::ExcessiveSeconded) => return Err(COST_EXCESSIVE_SECONDED), Err(ClusterRejectIncoming::CandidateUnknown | ClusterRejectIncoming::Duplicate) => - return Err(COST_UNEXPECTED_STATEMENT), + return Err(COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED), Err(ClusterRejectIncoming::NotInGroup) => { // sanity: shouldn't be possible; we already filtered this // out above. - return Err(COST_UNEXPECTED_STATEMENT) + return Err(COST_UNEXPECTED_STATEMENT_NOT_IN_GROUP) }, } }; @@ -1798,6 +1863,7 @@ fn handle_grid_statement( checked_statement.validator_index(), grid_sender_index, &checked_statement.payload(), + true, ); Ok(checked_statement) @@ -2376,6 +2442,7 @@ fn post_acknowledgement_statement_messages( statement.validator_index(), recipient, statement.payload(), + false, ); match peer.1.into() { ValidationVersion::V2 => messages.push(Versioned::V2( @@ -3255,6 +3322,7 @@ pub(crate) fn answer_request(state: &mut State, message: ResponderMessage) { statement.unchecked_validator_index(), validator_id, statement.unchecked_payload(), + false, ); } } diff --git a/polkadot/node/network/statement-distribution/src/v2/requests.rs b/polkadot/node/network/statement-distribution/src/v2/requests.rs index bed3d5c18ae2b1007a3ee585f00a54d1b98f7c27..bbcb268415e67141ff1c620a134582d06bd8a67f 100644 --- a/polkadot/node/network/statement-distribution/src/v2/requests.rs +++ b/polkadot/node/network/statement-distribution/src/v2/requests.rs @@ -315,7 +315,16 @@ impl RequestManager { request_props: impl Fn(&CandidateIdentifier) -> Option, peer_advertised: impl Fn(&CandidateIdentifier, &PeerId) -> Option, ) -> Option> { - if response_manager.len() >= MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS as usize { + // The number of parallel requests a node can answer is limited by + // `MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS`, however there is no + // need for the current node to limit itself to the same amount the + // requests, because the requests are going to different nodes anyways. + // While looking at https://github.com/paritytech/polkadot-sdk/issues/3314, + // found out that this requests take around 100ms to fullfill, so it + // would make sense to try to request things as early as we can, given + // we would need to request it for each candidate, around 25 right now + // on kusama. + if response_manager.len() >= 2 * MAX_PARALLEL_ATTESTED_CANDIDATE_REQUESTS as usize { return None } @@ -1027,6 +1036,7 @@ mod tests { let peer_advertised = |_identifier: &CandidateIdentifier, _peer: &_| { Some(StatementFilter::full(group_size)) }; + let outgoing = request_manager .next_request(&mut response_manager, request_props, peer_advertised) .unwrap(); @@ -1148,6 +1158,7 @@ mod tests { { let request_props = |_identifier: &CandidateIdentifier| Some((&request_properties).clone()); + let outgoing = request_manager .next_request(&mut response_manager, request_props, peer_advertised) .unwrap(); @@ -1230,6 +1241,7 @@ mod tests { { let request_props = |_identifier: &CandidateIdentifier| Some((&request_properties).clone()); + let outgoing = request_manager .next_request(&mut response_manager, request_props, peer_advertised) .unwrap(); diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs b/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs index 7ffed9d47d4bdeca1800f7d665970cd1882ea7b2..a944a9cd6d021364d3b9fa7b349e9a649866cf29 100644 --- a/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs +++ b/polkadot/node/network/statement-distribution/src/v2/tests/cluster.rs @@ -170,7 +170,7 @@ fn cluster_valid_statement_before_seconded_ignored() { overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) => { assert_eq!(p, peer_a); - assert_eq!(r, COST_UNEXPECTED_STATEMENT.into()); + assert_eq!(r, COST_UNEXPECTED_STATEMENT_CLUSTER_REJECTED.into()); } ); @@ -305,7 +305,7 @@ fn useful_cluster_statement_from_non_cluster_peer_rejected() { assert_matches!( overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_a && r == COST_UNEXPECTED_STATEMENT.into() => { } + if p == peer_a && r == COST_UNEXPECTED_STATEMENT_INVALID_SENDER.into() => { } ); overseer @@ -359,7 +359,7 @@ fn statement_from_non_cluster_originator_unexpected() { assert_matches!( overseer.recv().await, AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r))) - if p == peer_a && r == COST_UNEXPECTED_STATEMENT.into() => { } + if p == peer_a && r == COST_UNEXPECTED_STATEMENT_INVALID_SENDER.into() => { } ); overseer diff --git a/polkadot/node/primitives/Cargo.toml b/polkadot/node/primitives/Cargo.toml index 157594a5099261ca3ced1dedba965c7885b3b348..b4541bcc346c8ce4282947c38e0d9a2591631090 100644 --- a/polkadot/node/primitives/Cargo.toml +++ b/polkadot/node/primitives/Cargo.toml @@ -22,9 +22,9 @@ sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compres sp-runtime = { path = "../../../substrate/primitives/runtime" } polkadot-parachain-primitives = { path = "../../parachain", default-features = false } schnorrkel = "0.11.4" -thiserror = "1.0.48" +thiserror = { workspace = true } bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } -serde = { version = "1.0.195", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } [target.'cfg(not(target_os = "unknown"))'.dependencies] zstd = { version = "0.12.4", default-features = false } diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index e7fd2c46381499f221913a01e7a9c3bc1dacc2d3..6e3eefbcbe8c7bd6029c44e2cc43a2c30ea834f3 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -58,7 +58,7 @@ pub use disputes::{ /// relatively rare. /// /// The associated worker binaries should use the same version as the node that spawns them. -pub const NODE_VERSION: &'static str = "1.6.0"; +pub const NODE_VERSION: &'static str = "1.7.0"; // For a 16-ary Merkle Prefix Trie, we can expect at most 16 32-byte hashes per node // plus some overhead: diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 007e3dc595e9d49610e9d302fd28af712b27e300..8fd9f20b7bcfbde6a811fb7a4334242d9a1eb54f 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -83,11 +83,11 @@ futures = "0.3.21" hex-literal = "0.4.1" is_executable = "1.0.1" gum = { package = "tracing-gum", path = "../gum" } -log = "0.4.17" +log = { workspace = true, default-features = true } schnellru = "0.2.1" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } kvdb = "0.13.0" kvdb-rocksdb = { version = "0.19.0", optional = true } parity-db = { version = "0.4.12", optional = true } diff --git a/polkadot/node/service/src/chain_spec.rs b/polkadot/node/service/src/chain_spec.rs index 1b4a97290c6a71ea17b82cfc728323c60b980e87..af241d1cbc558c849bbf533dd644848b20fa4491 100644 --- a/polkadot/node/service/src/chain_spec.rs +++ b/polkadot/node/service/src/chain_spec.rs @@ -120,7 +120,7 @@ pub fn wococo_config() -> Result { fn default_parachains_host_configuration( ) -> polkadot_runtime_parachains::configuration::HostConfiguration { - use polkadot_primitives::{MAX_CODE_SIZE, MAX_POV_SIZE}; + use polkadot_primitives::{AsyncBackingParams, MAX_CODE_SIZE, MAX_POV_SIZE}; polkadot_runtime_parachains::configuration::HostConfiguration { validation_upgrade_cooldown: 2u32, @@ -151,6 +151,11 @@ fn default_parachains_host_configuration( relay_vrf_modulo_samples: 2, zeroth_delay_tranche_width: 0, minimum_validation_upgrade_delay: 5, + scheduling_lookahead: 2, + async_backing_params: AsyncBackingParams { + max_candidate_depth: 3, + allowed_ancestry_len: 2, + }, ..Default::default() } } diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index f7866f99363125d9f905a7a18056bed52e5d33e9..7de91d8cd5ded29a6ad2f2f033cd0dfc5ca580d5 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -35,12 +35,15 @@ async-trait = "0.1.57" sp-keystore = { path = "../../../substrate/primitives/keystore" } sc-keystore = { path = "../../../substrate/client/keystore" } sp-core = { path = "../../../substrate/primitives/core" } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" +bincode = "1.3.3" +sha1 = "0.10.6" +hex = "0.4.3" gum = { package = "tracing-gum", path = "../gum" } polkadot-erasure-coding = { package = "polkadot-erasure-coding", path = "../../erasure-coding" } -log = "0.4.17" +log = { workspace = true, default-features = true } env_logger = "0.9.0" rand = "0.8.5" # `rand` only supports uniform distribution, we need normal distribution for latency. @@ -62,8 +65,18 @@ itertools = "0.11.0" polkadot-primitives-test-helpers = { path = "../../primitives/test-helpers" } prometheus_endpoint = { package = "substrate-prometheus-endpoint", path = "../../../substrate/utils/prometheus" } prometheus = { version = "0.13.0", default-features = false } -serde = "1.0.195" -serde_yaml = "0.9" +serde = { workspace = true, default-features = true } +serde_yaml = { workspace = true } + +polkadot-node-core-approval-voting = { path = "../core/approval-voting" } +polkadot-approval-distribution = { path = "../network/approval-distribution" } +sp-consensus-babe = { path = "../../../substrate/primitives/consensus/babe" } +sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } +sp-timestamp = { path = "../../../substrate/primitives/timestamp" } + +schnorrkel = { version = "0.9.1", default-features = false } +rand_core = "0.6.2" # should match schnorrkel +rand_chacha = { version = "0.3.1" } paste = "1.0.14" orchestra = { version = "0.3.5", default-features = false, features = ["futures_channel"] } pyroscope = "0.5.7" diff --git a/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml new file mode 100644 index 0000000000000000000000000000000000000000..758c7fbbf1121a249ff3094bc74967e3ad2420da --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_no_shows.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + last_considered_tranche: 89 + coalesce_mean: 3.0 + coalesce_std_dev: 1.0 + stop_when_approved: true + coalesce_tranche_diff: 12 + workdir_prefix: "/tmp/" + enable_assignments_v2: true + num_no_shows_per_candidate: 10 + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9eeeefc53a4277aae51e44ee5940a2eb65111cd3 --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput.yaml @@ -0,0 +1,19 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 3.0 + coalesce_std_dev: 1.0 + enable_assignments_v2: true + last_considered_tranche: 89 + stop_when_approved: false + coalesce_tranche_diff: 12 + workdir_prefix: "/tmp" + num_no_shows_per_candidate: 0 + n_validators: 500 + n_cores: 100 + n_included_candidates: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml new file mode 100644 index 0000000000000000000000000000000000000000..370bb31a5c4c102174c29382ab6de70ca336278e --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput_best_case.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 3.0 + coalesce_std_dev: 1.0 + enable_assignments_v2: true + last_considered_tranche: 89 + stop_when_approved: true + coalesce_tranche_diff: 12 + workdir_prefix: "/tmp/" + num_no_shows_per_candidate: 0 + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml b/polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml new file mode 100644 index 0000000000000000000000000000000000000000..30b9ac8dc50fec25bc4952ef3706f9878aa2bbd7 --- /dev/null +++ b/polkadot/node/subsystem-bench/examples/approvals_throughput_no_optimisations_enabled.yaml @@ -0,0 +1,18 @@ +TestConfiguration: +# Test 1 +- objective: !ApprovalVoting + coalesce_mean: 1.0 + coalesce_std_dev: 0.0 + enable_assignments_v2: false + last_considered_tranche: 89 + stop_when_approved: false + coalesce_tranche_diff: 12 + workdir_prefix: "/tmp/" + num_no_shows_per_candidate: 0 + n_validators: 500 + n_cores: 100 + min_pov_size: 1120 + max_pov_size: 5120 + peer_bandwidth: 524288000000 + bandwidth: 524288000000 + num_blocks: 10 diff --git a/polkadot/node/subsystem-bench/src/approval/helpers.rs b/polkadot/node/subsystem-bench/src/approval/helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..623d91848f53f26e69ad7595c52a0e61466af6d1 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/helpers.rs @@ -0,0 +1,207 @@ +// Copyright (C) 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 crate::core::configuration::TestAuthorities; +use itertools::Itertools; +use polkadot_node_core_approval_voting::time::{Clock, SystemClock, Tick}; +use polkadot_node_network_protocol::{ + grid_topology::{SessionGridTopology, TopologyPeerInfo}, + View, +}; +use polkadot_node_subsystem_types::messages::{ + network_bridge_event::NewGossipTopology, ApprovalDistributionMessage, NetworkBridgeEvent, +}; +use polkadot_overseer::AllMessages; +use polkadot_primitives::{ + BlockNumber, CandidateEvent, CandidateReceipt, CoreIndex, GroupIndex, Hash, Header, + Id as ParaId, Slot, ValidatorIndex, +}; +use polkadot_primitives_test_helpers::dummy_candidate_receipt_bad_sig; +use rand::{seq::SliceRandom, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use sc_network::PeerId; +use sp_consensus_babe::{ + digests::{CompatibleDigestItem, PreDigest, SecondaryVRFPreDigest}, + AllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch, VrfSignature, VrfTranscript, +}; +use sp_core::crypto::VrfSecret; +use sp_keyring::sr25519::Keyring as Sr25519Keyring; +use sp_runtime::{Digest, DigestItem}; +use std::sync::{atomic::AtomicU64, Arc}; + +/// A fake system clock used for driving the approval voting and make +/// it process blocks, assignments and approvals from the past. +#[derive(Clone)] +pub struct PastSystemClock { + /// The real system clock + real_system_clock: SystemClock, + /// The difference in ticks between the real system clock and the current clock. + delta_ticks: Arc, +} + +impl PastSystemClock { + /// Creates a new fake system clock with `delta_ticks` between the real time and the fake one. + pub fn new(real_system_clock: SystemClock, delta_ticks: Arc) -> Self { + PastSystemClock { real_system_clock, delta_ticks } + } +} + +impl Clock for PastSystemClock { + fn tick_now(&self) -> Tick { + self.real_system_clock.tick_now() - + self.delta_ticks.load(std::sync::atomic::Ordering::SeqCst) + } + + fn wait( + &self, + tick: Tick, + ) -> std::pin::Pin + Send + 'static>> { + self.real_system_clock + .wait(tick + self.delta_ticks.load(std::sync::atomic::Ordering::SeqCst)) + } +} + +/// Helper function to generate a babe epoch for this benchmark. +/// It does not change for the duration of the test. +pub fn generate_babe_epoch(current_slot: Slot, authorities: TestAuthorities) -> BabeEpoch { + let authorities = authorities + .validator_babe_id + .into_iter() + .enumerate() + .map(|(index, public)| (public, index as u64)) + .collect_vec(); + BabeEpoch { + epoch_index: 1, + start_slot: current_slot.saturating_sub(1u64), + duration: 200, + authorities, + randomness: [0xde; 32], + config: BabeEpochConfiguration { c: (1, 4), allowed_slots: AllowedSlots::PrimarySlots }, + } +} + +/// Generates a topology to be used for this benchmark. +pub fn generate_topology(test_authorities: &TestAuthorities) -> SessionGridTopology { + let keyrings = test_authorities + .validator_authority_id + .clone() + .into_iter() + .zip(test_authorities.peer_ids.clone()) + .collect_vec(); + + let topology = keyrings + .clone() + .into_iter() + .enumerate() + .map(|(index, (discovery_id, peer_id))| TopologyPeerInfo { + peer_ids: vec![peer_id], + validator_index: ValidatorIndex(index as u32), + discovery_id, + }) + .collect_vec(); + let shuffled = (0..keyrings.len()).collect_vec(); + + SessionGridTopology::new(shuffled, topology) +} + +/// Generates new session topology message. +pub fn generate_new_session_topology( + test_authorities: &TestAuthorities, + test_node: ValidatorIndex, +) -> Vec { + let topology = generate_topology(test_authorities); + + let event = NetworkBridgeEvent::NewGossipTopology(NewGossipTopology { + session: 1, + topology, + local_index: Some(test_node), + }); + vec![AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(event))] +} + +/// Generates a peer view change for the passed `block_hash` +pub fn generate_peer_view_change_for(block_hash: Hash, peer_id: PeerId) -> AllMessages { + let network = NetworkBridgeEvent::PeerViewChange(peer_id, View::new([block_hash], 0)); + + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate(network)) +} + +/// Helper function to create a a signature for the block header. +fn garbage_vrf_signature() -> VrfSignature { + let transcript = VrfTranscript::new(b"test-garbage", &[]); + Sr25519Keyring::Alice.pair().vrf_sign(&transcript.into()) +} + +/// Helper function to create a block header. +pub fn make_header(parent_hash: Hash, slot: Slot, number: u32) -> Header { + let digest = + { + let mut digest = Digest::default(); + let vrf_signature = garbage_vrf_signature(); + digest.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF( + SecondaryVRFPreDigest { authority_index: 0, slot, vrf_signature }, + ))); + digest + }; + + Header { + digest, + extrinsics_root: Default::default(), + number, + state_root: Default::default(), + parent_hash, + } +} + +/// Helper function to create a candidate receipt. +fn make_candidate(para_id: ParaId, hash: &Hash) -> CandidateReceipt { + let mut r = dummy_candidate_receipt_bad_sig(*hash, Some(Default::default())); + r.descriptor.para_id = para_id; + r +} + +/// Helper function to create a list of candidates that are included in the block +pub fn make_candidates( + block_hash: Hash, + block_number: BlockNumber, + num_cores: u32, + num_candidates: u32, +) -> Vec { + let seed = [block_number as u8; 32]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + let mut candidates = (0..num_cores) + .map(|core| { + CandidateEvent::CandidateIncluded( + make_candidate(ParaId::from(core), &block_hash), + Vec::new().into(), + CoreIndex(core), + GroupIndex(core), + ) + }) + .collect_vec(); + let (candidates, _) = candidates.partial_shuffle(&mut rand_chacha, num_candidates as usize); + candidates + .iter_mut() + .map(|val| val.clone()) + .sorted_by(|a, b| match (a, b) { + ( + CandidateEvent::CandidateIncluded(_, _, core_a, _), + CandidateEvent::CandidateIncluded(_, _, core_b, _), + ) => core_a.0.cmp(&core_b.0), + (_, _) => todo!("Should not happen"), + }) + .collect_vec() +} diff --git a/polkadot/node/subsystem-bench/src/approval/message_generator.rs b/polkadot/node/subsystem-bench/src/approval/message_generator.rs new file mode 100644 index 0000000000000000000000000000000000000000..a71034013247678aeb9aced2619f8d1d6d29ebe9 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/message_generator.rs @@ -0,0 +1,683 @@ +// Copyright (C) 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 crate::{ + approval::{ + helpers::{generate_babe_epoch, generate_topology}, + test_message::{MessagesBundle, TestMessageInfo}, + ApprovalTestState, BlockTestData, GeneratedState, BUFFER_FOR_GENERATION_MILLIS, LOG_TARGET, + SLOT_DURATION_MILLIS, + }, + core::{ + configuration::{TestAuthorities, TestConfiguration}, + mock::runtime_api::session_info_for_peers, + NODE_UNDER_TEST, + }, + ApprovalsOptions, TestObjective, +}; +use futures::SinkExt; +use itertools::Itertools; +use parity_scale_codec::Encode; +use polkadot_node_core_approval_voting::{ + criteria::{compute_assignments, Config}, + time::tranche_to_tick, +}; +use polkadot_node_network_protocol::{ + grid_topology::{GridNeighbors, RandomRouting, RequiredRouting, SessionGridTopology}, + v3 as protocol_v3, +}; +use polkadot_node_primitives::approval::{ + self, + v2::{CoreBitfield, IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2}, +}; +use polkadot_primitives::{ + vstaging::ApprovalVoteMultipleCandidates, CandidateEvent, CandidateHash, CandidateIndex, + CoreIndex, Hash, SessionInfo, Slot, ValidatorId, ValidatorIndex, ASSIGNMENT_KEY_TYPE_ID, +}; +use rand::{seq::SliceRandom, RngCore, SeedableRng}; +use rand_chacha::ChaCha20Rng; +use rand_distr::{Distribution, Normal}; +use sc_keystore::LocalKeystore; +use sc_network::PeerId; +use sc_service::SpawnTaskHandle; +use sha1::Digest; +use sp_application_crypto::AppCrypto; +use sp_consensus_babe::SlotDuration; +use sp_keystore::Keystore; +use sp_timestamp::Timestamp; +use std::{ + cmp::max, + collections::{BTreeMap, HashSet}, + fs, + io::Write, + path::{Path, PathBuf}, + time::Duration, +}; + +/// A generator of messages coming from a given Peer/Validator +pub struct PeerMessagesGenerator { + /// The grid neighbors of the node under test. + pub topology_node_under_test: GridNeighbors, + /// The topology of the network for the epoch under test. + pub topology: SessionGridTopology, + /// The validator index for this object generates the messages. + pub validator_index: ValidatorIndex, + /// An array of pre-generated random samplings, that is used to determine, which nodes would + /// send a given assignment, to the node under test because of the random samplings. + /// As an optimization we generate this sampling at the begining of the test and just pick + /// one randomly, because always taking the samples would be too expensive for benchamrk. + pub random_samplings: Vec>, + /// Channel for sending the generated messages to the aggregator + pub tx_messages: futures::channel::mpsc::UnboundedSender<(Hash, Vec)>, + /// The list of test authorities + pub test_authorities: TestAuthorities, + //// The session info used for the test. + pub session_info: SessionInfo, + /// The blocks used for testing + pub blocks: Vec, + /// Approval options params. + pub options: ApprovalsOptions, +} + +impl PeerMessagesGenerator { + /// Generates messages by spawning a blocking task in the background which begins creating + /// the assignments/approvals and peer view changes at the begining of each block. + pub fn generate_messages(mut self, spawn_task_handle: &SpawnTaskHandle) { + spawn_task_handle.spawn("generate-messages", "generate-messages", async move { + for block_info in &self.blocks { + let assignments = self.generate_assignments(block_info); + + let bytes = self.validator_index.0.to_be_bytes(); + let seed = [ + bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + let approvals = issue_approvals( + assignments, + block_info.hash, + &self.test_authorities.validator_public, + block_info.candidates.clone(), + &self.options, + &mut rand_chacha, + self.test_authorities.keyring.keystore_ref(), + ); + + self.tx_messages + .send((block_info.hash, approvals)) + .await + .expect("Should not fail"); + } + }) + } + + // Builds the messages finger print corresponding to this configuration. + // When the finger print exists already on disk the messages are not re-generated. + fn messages_fingerprint( + configuration: &TestConfiguration, + options: &ApprovalsOptions, + ) -> String { + let mut fingerprint = options.fingerprint(); + let mut exclude_objective = configuration.clone(); + // The objective contains the full content of `ApprovalOptions`, we don't want to put all of + // that in fingerprint, so execlute it because we add it manually see above. + exclude_objective.objective = TestObjective::Unimplemented; + let configuration_bytes = bincode::serialize(&exclude_objective).unwrap(); + fingerprint.extend(configuration_bytes); + let mut sha1 = sha1::Sha1::new(); + sha1.update(fingerprint); + let result = sha1.finalize(); + hex::encode(result) + } + + /// Generate all messages(Assignments & Approvals) needed for approving `blocks``. + pub fn generate_messages_if_needed( + configuration: &TestConfiguration, + test_authorities: &TestAuthorities, + options: &ApprovalsOptions, + spawn_task_handle: &SpawnTaskHandle, + ) -> PathBuf { + let path_name = format!( + "{}/{}", + options.workdir_prefix, + Self::messages_fingerprint(configuration, options) + ); + + let path = Path::new(&path_name); + if path.exists() { + return path.to_path_buf(); + } + + gum::info!("Generate message because file does not exist"); + let delta_to_first_slot_under_test = Timestamp::new(BUFFER_FOR_GENERATION_MILLIS); + let initial_slot = Slot::from_timestamp( + (*Timestamp::current() - *delta_to_first_slot_under_test).into(), + SlotDuration::from_millis(SLOT_DURATION_MILLIS), + ); + + let babe_epoch = generate_babe_epoch(initial_slot, test_authorities.clone()); + let session_info = session_info_for_peers(configuration, test_authorities); + let blocks = ApprovalTestState::generate_blocks_information( + configuration, + &babe_epoch, + initial_slot, + ); + + gum::info!(target: LOG_TARGET, "Generate messages"); + let topology = generate_topology(test_authorities); + + let random_samplings = random_samplings_to_node( + ValidatorIndex(NODE_UNDER_TEST), + test_authorities.validator_public.len(), + test_authorities.validator_public.len() * 2, + ); + + let topology_node_under_test = + topology.compute_grid_neighbors_for(ValidatorIndex(NODE_UNDER_TEST)).unwrap(); + + let (tx, mut rx) = futures::channel::mpsc::unbounded(); + + // Spawn a thread to generate the messages for each validator, so that we speed up the + // generation. + for current_validator_index in 1..test_authorities.validator_public.len() { + let peer_message_source = PeerMessagesGenerator { + topology_node_under_test: topology_node_under_test.clone(), + topology: topology.clone(), + validator_index: ValidatorIndex(current_validator_index as u32), + test_authorities: test_authorities.clone(), + session_info: session_info.clone(), + blocks: blocks.clone(), + tx_messages: tx.clone(), + random_samplings: random_samplings.clone(), + options: options.clone(), + }; + + peer_message_source.generate_messages(spawn_task_handle); + } + + std::mem::drop(tx); + + let seed = [0x32; 32]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + + let mut all_messages: BTreeMap> = BTreeMap::new(); + // Receive all messages and sort them by Tick they have to be sent. + loop { + match rx.try_next() { + Ok(Some((block_hash, messages))) => + for message in messages { + let block_info = blocks + .iter() + .find(|val| val.hash == block_hash) + .expect("Should find blocks"); + let tick_to_send = tranche_to_tick( + SLOT_DURATION_MILLIS, + block_info.slot, + message.tranche_to_send(), + ); + let to_add = all_messages.entry(tick_to_send).or_default(); + to_add.push(message); + }, + Ok(None) => break, + Err(_) => { + std::thread::sleep(Duration::from_millis(50)); + }, + } + } + let all_messages = all_messages + .into_iter() + .flat_map(|(_, mut messages)| { + // Shuffle the messages inside the same tick, so that we don't priorites messages + // for older nodes. we try to simulate the same behaviour as in real world. + messages.shuffle(&mut rand_chacha); + messages + }) + .collect_vec(); + + gum::info!("Generated a number of {:} unique messages", all_messages.len()); + + let generated_state = GeneratedState { all_messages: Some(all_messages), initial_slot }; + + let mut messages_file = fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + .unwrap(); + + messages_file + .write_all(&generated_state.encode()) + .expect("Could not update message file"); + path.to_path_buf() + } + + /// Generates assignments for the given `current_validator_index` + /// Returns a list of assignments to be sent sorted by tranche. + fn generate_assignments(&self, block_info: &BlockTestData) -> Vec { + let config = Config::from(&self.session_info); + + let leaving_cores = block_info + .candidates + .clone() + .into_iter() + .map(|candidate_event| { + if let CandidateEvent::CandidateIncluded(candidate, _, core_index, group_index) = + candidate_event + { + (candidate.hash(), core_index, group_index) + } else { + todo!("Variant is never created in this benchmark") + } + }) + .collect_vec(); + + let mut assignments_by_tranche = BTreeMap::new(); + + let bytes = self.validator_index.0.to_be_bytes(); + let seed = [ + bytes[0], bytes[1], bytes[2], bytes[3], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + + let to_be_sent_by = neighbours_that_would_sent_message( + &self.test_authorities.peer_ids, + self.validator_index.0, + &self.topology_node_under_test, + &self.topology, + ); + + let leaving_cores = leaving_cores + .clone() + .into_iter() + .filter(|(_, core_index, _group_index)| core_index.0 != self.validator_index.0) + .collect_vec(); + + let store = LocalKeystore::in_memory(); + let _public = store + .sr25519_generate_new( + ASSIGNMENT_KEY_TYPE_ID, + Some(self.test_authorities.key_seeds[self.validator_index.0 as usize].as_str()), + ) + .expect("should not fail"); + let assignments = compute_assignments( + &store, + block_info.relay_vrf_story.clone(), + &config, + leaving_cores.clone(), + self.options.enable_assignments_v2, + ); + + let random_sending_nodes = self + .random_samplings + .get(rand_chacha.next_u32() as usize % self.random_samplings.len()) + .unwrap(); + let random_sending_peer_ids = random_sending_nodes + .iter() + .map(|validator| (*validator, self.test_authorities.peer_ids[validator.0 as usize])) + .collect_vec(); + + let mut unique_assignments = HashSet::new(); + for (core_index, assignment) in assignments { + let assigned_cores = match &assignment.cert().kind { + approval::v2::AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => + core_bitfield.iter_ones().map(|val| CoreIndex::from(val as u32)).collect_vec(), + approval::v2::AssignmentCertKindV2::RelayVRFDelay { core_index } => + vec![*core_index], + approval::v2::AssignmentCertKindV2::RelayVRFModulo { sample: _ } => + vec![core_index], + }; + + let bitfiled: CoreBitfield = assigned_cores.clone().try_into().unwrap(); + + // For the cases where tranch0 assignments are in a single certificate we need to make + // sure we create a single message. + if unique_assignments.insert(bitfiled) { + let this_tranche_assignments = + assignments_by_tranche.entry(assignment.tranche()).or_insert_with(Vec::new); + + this_tranche_assignments.push(( + IndirectAssignmentCertV2 { + block_hash: block_info.hash, + validator: self.validator_index, + cert: assignment.cert().clone(), + }, + block_info + .candidates + .iter() + .enumerate() + .filter(|(_index, candidate)| { + if let CandidateEvent::CandidateIncluded(_, _, core, _) = candidate { + assigned_cores.contains(core) + } else { + panic!("Should not happen"); + } + }) + .map(|(index, _)| index as u32) + .collect_vec() + .try_into() + .unwrap(), + to_be_sent_by + .iter() + .chain(random_sending_peer_ids.iter()) + .copied() + .collect::>(), + assignment.tranche(), + )); + } + } + + assignments_by_tranche + .into_values() + .flat_map(|assignments| assignments.into_iter()) + .map(|assignment| { + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(vec![( + assignment.0, + assignment.1, + )]); + TestMessageInfo { + msg, + sent_by: assignment + .2 + .into_iter() + .map(|(validator_index, _)| validator_index) + .collect_vec(), + tranche: assignment.3, + block_hash: block_info.hash, + } + }) + .collect_vec() + } +} + +/// A list of random samplings that we use to determine which nodes should send a given message to +/// the node under test. +/// We can not sample every time for all the messages because that would be too expensive to +/// perform, so pre-generate a list of samples for a given network size. +/// - result[i] give us as a list of random nodes that would send a given message to the node under +/// test. +fn random_samplings_to_node( + node_under_test: ValidatorIndex, + num_validators: usize, + num_samplings: usize, +) -> Vec> { + let seed = [7u8; 32]; + let mut rand_chacha = ChaCha20Rng::from_seed(seed); + + (0..num_samplings) + .map(|_| { + (0..num_validators) + .filter(|sending_validator_index| { + *sending_validator_index != NODE_UNDER_TEST as usize + }) + .flat_map(|sending_validator_index| { + let mut validators = (0..num_validators).collect_vec(); + validators.shuffle(&mut rand_chacha); + + let mut random_routing = RandomRouting::default(); + validators + .into_iter() + .flat_map(|validator_to_send| { + if random_routing.sample(num_validators, &mut rand_chacha) { + random_routing.inc_sent(); + if validator_to_send == node_under_test.0 as usize { + Some(ValidatorIndex(sending_validator_index as u32)) + } else { + None + } + } else { + None + } + }) + .collect_vec() + }) + .collect_vec() + }) + .collect_vec() +} + +/// Helper function to randomly determine how many approvals we coalesce together in a single +/// message. +fn coalesce_approvals_len( + coalesce_mean: f32, + coalesce_std_dev: f32, + rand_chacha: &mut ChaCha20Rng, +) -> usize { + max( + 1, + Normal::new(coalesce_mean, coalesce_std_dev) + .expect("normal distribution parameters are good") + .sample(rand_chacha) + .round() as i32, + ) as usize +} + +/// Helper function to create approvals signatures for all assignments passed as arguments. +/// Returns a list of Approvals messages that need to be sent. +fn issue_approvals( + assignments: Vec, + block_hash: Hash, + validator_ids: &[ValidatorId], + candidates: Vec, + options: &ApprovalsOptions, + rand_chacha: &mut ChaCha20Rng, + store: &LocalKeystore, +) -> Vec { + let mut queued_to_sign: Vec = Vec::new(); + let mut num_coalesce = + coalesce_approvals_len(options.coalesce_mean, options.coalesce_std_dev, rand_chacha); + let result = assignments + .iter() + .enumerate() + .map(|(_index, message)| match &message.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { + let mut approvals_to_create = Vec::new(); + + let current_validator_index = queued_to_sign + .first() + .map(|msg| msg.validator_index) + .unwrap_or(ValidatorIndex(99999)); + + // Invariant for this benchmark. + assert_eq!(assignments.len(), 1); + + let assignment = assignments.first().unwrap(); + + let earliest_tranche = queued_to_sign + .first() + .map(|val| val.assignment.tranche) + .unwrap_or(message.tranche); + + if queued_to_sign.len() >= num_coalesce || + (!queued_to_sign.is_empty() && + current_validator_index != assignment.0.validator) || + message.tranche - earliest_tranche >= options.coalesce_tranche_diff + { + approvals_to_create.push(TestSignInfo::sign_candidates( + &mut queued_to_sign, + validator_ids, + block_hash, + num_coalesce, + store, + )); + num_coalesce = coalesce_approvals_len( + options.coalesce_mean, + options.coalesce_std_dev, + rand_chacha, + ); + } + + // If more that one candidate was in the assignment queue all of them for issuing + // approvals + for candidate_index in assignment.1.iter_ones() { + let candidate = candidates.get(candidate_index).unwrap(); + if let CandidateEvent::CandidateIncluded(candidate, _, _, _) = candidate { + queued_to_sign.push(TestSignInfo { + candidate_hash: candidate.hash(), + candidate_index: candidate_index as CandidateIndex, + validator_index: assignment.0.validator, + assignment: message.clone(), + }); + } else { + todo!("Other enum variants are not used in this benchmark"); + } + } + approvals_to_create + }, + _ => { + todo!("Other enum variants are not used in this benchmark"); + }, + }) + .collect_vec(); + + let mut messages = result.into_iter().flatten().collect_vec(); + + if !queued_to_sign.is_empty() { + messages.push(TestSignInfo::sign_candidates( + &mut queued_to_sign, + validator_ids, + block_hash, + num_coalesce, + store, + )); + } + messages +} + +/// Helper struct to gather information about more than one candidate an sign it in a single +/// approval message. +struct TestSignInfo { + /// The candidate hash + candidate_hash: CandidateHash, + /// The candidate index + candidate_index: CandidateIndex, + /// The validator sending the assignments + validator_index: ValidatorIndex, + /// The assignments convering this candidate + assignment: TestMessageInfo, +} + +impl TestSignInfo { + /// Helper function to create a signture for all candidates in `to_sign` parameter. + /// Returns a TestMessage + fn sign_candidates( + to_sign: &mut Vec, + validator_ids: &[ValidatorId], + block_hash: Hash, + num_coalesce: usize, + store: &LocalKeystore, + ) -> MessagesBundle { + let current_validator_index = to_sign.first().map(|val| val.validator_index).unwrap(); + let tranche_approval_can_be_sent = + to_sign.iter().map(|val| val.assignment.tranche).max().unwrap(); + let validator_id = validator_ids.get(current_validator_index.0 as usize).unwrap().clone(); + + let unique_assignments: HashSet = + to_sign.iter().map(|info| info.assignment.clone()).collect(); + + let mut to_sign = to_sign + .drain(..) + .sorted_by(|val1, val2| val1.candidate_index.cmp(&val2.candidate_index)) + .peekable(); + + let mut bundle = MessagesBundle { + assignments: unique_assignments.into_iter().collect_vec(), + approvals: Vec::new(), + }; + + while to_sign.peek().is_some() { + let to_sign = to_sign.by_ref().take(num_coalesce).collect_vec(); + + let hashes = to_sign.iter().map(|val| val.candidate_hash).collect_vec(); + let candidate_indices = to_sign.iter().map(|val| val.candidate_index).collect_vec(); + + let sent_by = to_sign + .iter() + .flat_map(|val| val.assignment.sent_by.iter()) + .copied() + .collect::>(); + + let payload = ApprovalVoteMultipleCandidates(&hashes).signing_payload(1); + + let signature = store + .sr25519_sign(ValidatorId::ID, &validator_id.clone().into(), &payload[..]) + .unwrap() + .unwrap() + .into(); + let indirect = IndirectSignedApprovalVoteV2 { + block_hash, + candidate_indices: candidate_indices.try_into().unwrap(), + validator: current_validator_index, + signature, + }; + let msg = protocol_v3::ApprovalDistributionMessage::Approvals(vec![indirect]); + + bundle.approvals.push(TestMessageInfo { + msg, + sent_by: sent_by.into_iter().collect_vec(), + tranche: tranche_approval_can_be_sent, + block_hash, + }); + } + bundle + } +} + +/// Determine what neighbours would send a given message to the node under test. +fn neighbours_that_would_sent_message( + peer_ids: &[PeerId], + current_validator_index: u32, + topology_node_under_test: &GridNeighbors, + topology: &SessionGridTopology, +) -> Vec<(ValidatorIndex, PeerId)> { + let topology_originator = topology + .compute_grid_neighbors_for(ValidatorIndex(current_validator_index)) + .unwrap(); + + let originator_y = topology_originator.validator_indices_y.iter().find(|validator| { + topology_node_under_test.required_routing_by_index(**validator, false) == + RequiredRouting::GridY + }); + + assert!(originator_y != Some(&ValidatorIndex(NODE_UNDER_TEST))); + + let originator_x = topology_originator.validator_indices_x.iter().find(|validator| { + topology_node_under_test.required_routing_by_index(**validator, false) == + RequiredRouting::GridX + }); + + assert!(originator_x != Some(&ValidatorIndex(NODE_UNDER_TEST))); + + let is_neighbour = topology_originator + .validator_indices_x + .contains(&ValidatorIndex(NODE_UNDER_TEST)) || + topology_originator + .validator_indices_y + .contains(&ValidatorIndex(NODE_UNDER_TEST)); + + let mut to_be_sent_by = originator_y + .into_iter() + .chain(originator_x) + .map(|val| (*val, peer_ids[val.0 as usize])) + .collect_vec(); + + if is_neighbour { + to_be_sent_by.push((ValidatorIndex(current_validator_index), peer_ids[0])); + } + + to_be_sent_by +} diff --git a/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs b/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs new file mode 100644 index 0000000000000000000000000000000000000000..77ba80d4b2bbcbc7616c00ad6f5069ceb22979bc --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/mock_chain_selection.rs @@ -0,0 +1,64 @@ +// Copyright (C) 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 crate::approval::{ApprovalTestState, PastSystemClock, LOG_TARGET, SLOT_DURATION_MILLIS}; +use futures::FutureExt; +use polkadot_node_core_approval_voting::time::{slot_number_to_tick, Clock, TICK_DURATION_MILLIS}; +use polkadot_node_subsystem::{overseer, SpawnedSubsystem, SubsystemError}; +use polkadot_node_subsystem_types::messages::ChainSelectionMessage; + +/// Mock ChainSelection subsystem used to answer request made by the approval-voting subsystem, +/// during benchmark. All the necessary information to answer the requests is stored in the `state` +pub struct MockChainSelection { + pub state: ApprovalTestState, + pub clock: PastSystemClock, +} + +#[overseer::subsystem(ChainSelection, error=SubsystemError, prefix=self::overseer)] +impl MockChainSelection { + fn start(self, ctx: Context) -> SpawnedSubsystem { + let future = self.run(ctx).map(|_| Ok(())).boxed(); + + SpawnedSubsystem { name: "mock-chain-subsystem", future } + } +} + +#[overseer::contextbounds(ChainSelection, prefix = self::overseer)] +impl MockChainSelection { + async fn run(self, mut ctx: Context) { + loop { + let msg = ctx.recv().await.expect("Should not fail"); + match msg { + orchestra::FromOrchestra::Signal(_) => {}, + orchestra::FromOrchestra::Communication { msg } => + if let ChainSelectionMessage::Approved(hash) = msg { + let block_info = self.state.get_info_by_hash(hash); + let approved_number = block_info.block_number; + + block_info.approved.store(true, std::sync::atomic::Ordering::SeqCst); + self.state + .last_approved_block + .store(approved_number, std::sync::atomic::Ordering::SeqCst); + + let approved_in_tick = self.clock.tick_now() - + slot_number_to_tick(SLOT_DURATION_MILLIS, block_info.slot); + + gum::info!(target: LOG_TARGET, ?hash, "Chain selection approved after {:} ms", approved_in_tick * TICK_DURATION_MILLIS); + }, + } + } + } +} diff --git a/polkadot/node/subsystem-bench/src/approval/mod.rs b/polkadot/node/subsystem-bench/src/approval/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..f07912de1887580a58770b9bf9e76624c60bdcc4 --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/mod.rs @@ -0,0 +1,1070 @@ +// Copyright (C) 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 crate::{ + approval::{ + helpers::{ + generate_babe_epoch, generate_new_session_topology, generate_peer_view_change_for, + make_header, PastSystemClock, + }, + message_generator::PeerMessagesGenerator, + mock_chain_selection::MockChainSelection, + test_message::{MessagesBundle, TestMessageInfo}, + }, + core::{ + configuration::TestAuthorities, + environment::{ + BenchmarkUsage, TestEnvironment, TestEnvironmentDependencies, MAX_TIME_OF_FLIGHT, + }, + mock::{ + chain_api::{ChainApiState, MockChainApi}, + dummy_builder, + network_bridge::{MockNetworkBridgeRx, MockNetworkBridgeTx}, + runtime_api::MockRuntimeApi, + AlwaysSupportsParachains, TestSyncOracle, + }, + network::{ + new_network, HandleNetworkMessage, NetworkEmulatorHandle, NetworkInterface, + NetworkInterfaceReceiver, + }, + NODE_UNDER_TEST, + }, + TestConfiguration, +}; +use colored::Colorize; +use futures::channel::oneshot; +use itertools::Itertools; +use orchestra::TimeoutExt; +use overseer::{metrics::Metrics as OverseerMetrics, MetricsTrait}; +use parity_scale_codec::{Decode, Encode}; +use polkadot_approval_distribution::ApprovalDistribution; +use polkadot_node_core_approval_voting::{ + time::{slot_number_to_tick, tick_to_slot_number, Clock, ClockExt, SystemClock}, + ApprovalVotingSubsystem, Config as ApprovalVotingConfig, Metrics as ApprovalVotingMetrics, +}; +use polkadot_node_network_protocol::v3 as protocol_v3; +use polkadot_node_primitives::approval::{self, v1::RelayVRFStory}; +use polkadot_node_subsystem::{overseer, AllMessages, Overseer, OverseerConnector, SpawnGlue}; +use polkadot_node_subsystem_test_helpers::mock::new_block_import_info; +use polkadot_node_subsystem_types::messages::{ApprovalDistributionMessage, ApprovalVotingMessage}; +use polkadot_node_subsystem_util::metrics::Metrics; +use polkadot_overseer::Handle as OverseerHandleReal; +use polkadot_primitives::{ + BlockNumber, CandidateEvent, CandidateIndex, CandidateReceipt, Hash, Header, Slot, + ValidatorIndex, +}; +use prometheus::Registry; +use sc_keystore::LocalKeystore; +use sc_service::SpawnTaskHandle; +use serde::{Deserialize, Serialize}; +use sp_consensus_babe::Epoch as BabeEpoch; +use sp_core::H256; +use std::{ + cmp::max, + collections::{HashMap, HashSet}, + fs, + io::Read, + ops::Sub, + sync::{ + atomic::{AtomicBool, AtomicU32, AtomicU64}, + Arc, + }, + time::{Duration, Instant}, +}; +use tokio::time::sleep; + +mod helpers; +mod message_generator; +mod mock_chain_selection; +mod test_message; + +pub(crate) const LOG_TARGET: &str = "subsystem-bench::approval"; +pub(crate) const NUM_COLUMNS: u32 = 1; +pub(crate) const SLOT_DURATION_MILLIS: u64 = 6000; +pub(crate) const TEST_CONFIG: ApprovalVotingConfig = ApprovalVotingConfig { + col_approval_data: DATA_COL, + slot_duration_millis: SLOT_DURATION_MILLIS, +}; + +const DATA_COL: u32 = 0; + +/// Start generating messages for a slot into the future, so that the +/// generation nevers falls behind the current slot. +const BUFFER_FOR_GENERATION_MILLIS: u64 = 30_000; + +/// Parameters specific to the approvals benchmark +#[derive(Debug, Clone, Serialize, Deserialize, clap::Parser)] +#[clap(rename_all = "kebab-case")] +#[allow(missing_docs)] +pub struct ApprovalsOptions { + #[clap(short, long, default_value_t = 89)] + /// The last considered tranche for which we send the message. + pub last_considered_tranche: u32, + #[clap(short, long, default_value_t = 1.0)] + /// Min candidates to be signed in a single approval. + pub coalesce_mean: f32, + #[clap(short, long, default_value_t = 1.0)] + /// Max candidate to be signed in a single approval. + pub coalesce_std_dev: f32, + /// The maximum tranche diff between approvals coalesced toghther. + pub coalesce_tranche_diff: u32, + #[clap(short, long, default_value_t = false)] + /// Enable assignments v2. + pub enable_assignments_v2: bool, + #[clap(short, long, default_value_t = true)] + /// Sends messages only till block is approved. + pub stop_when_approved: bool, + #[clap(short, long)] + /// Work directory. + #[clap(short, long, default_value_t = format!("/tmp"))] + pub workdir_prefix: String, + /// The number of no shows per candidate + #[clap(short, long, default_value_t = 0)] + pub num_no_shows_per_candidate: u32, +} + +impl ApprovalsOptions { + // Generates a fingerprint use to determine if messages need to be re-generated. + fn fingerprint(&self) -> Vec { + let mut bytes = Vec::new(); + bytes.extend(self.coalesce_mean.to_be_bytes()); + bytes.extend(self.coalesce_std_dev.to_be_bytes()); + bytes.extend(self.coalesce_tranche_diff.to_be_bytes()); + bytes.extend((self.enable_assignments_v2 as i32).to_be_bytes()); + bytes + } +} + +/// Information about a block. It is part of test state and it is used by the mock +/// subsystems to be able to answer the calls approval-voting and approval-distribution +/// do into the outside world. +#[derive(Clone, Debug)] +struct BlockTestData { + /// The slot this block occupies, see implementer's guide to understand what a slot + /// is in the context of polkadot. + slot: Slot, + /// The hash of the block. + hash: Hash, + /// The block number. + block_number: BlockNumber, + /// The list of candidates included in this block. + candidates: Vec, + /// The block header. + header: Header, + /// The vrf story for the given block. + relay_vrf_story: RelayVRFStory, + /// If the block has been approved by the approval-voting subsystem. + /// This set on `true` when ChainSelectionMessage::Approved is received inside the chain + /// selection mock subsystem. + approved: Arc, + /// The total number of candidates before this block. + total_candidates_before: u64, + /// The votes we sent. + /// votes[validator_index][candidate_index] tells if validator sent vote for candidate. + /// We use this to mark the test as succesfull if GetApprovalSignatures returns all the votes + /// from here. + votes: Arc>>, +} + +/// Candidate information used during the test to decide if more messages are needed. +#[derive(Debug)] +struct CandidateTestData { + /// The configured maximum number of no-shows for this candidate. + max_no_shows: u32, + /// The last tranche where we had a no-show. + last_tranche_with_no_show: u32, + /// The number of sent assignments. + sent_assignment: u32, + /// The number of no-shows. + num_no_shows: u32, + /// The maximum tranche were we covered the needed approvals + max_tranche: u32, + /// Minimum needed votes to approve candidate. + needed_approvals: u32, +} + +impl CandidateTestData { + /// If message in this tranche needs to be sent. + fn should_send_tranche(&self, tranche: u32) -> bool { + self.sent_assignment <= self.needed_approvals || + tranche <= self.max_tranche + self.num_no_shows + } + + /// Sets max tranche + fn set_max_tranche(&mut self, tranche: u32) { + self.max_tranche = max(tranche, self.max_tranche); + } + + /// Records no-show for candidate. + fn record_no_show(&mut self, tranche: u32) { + self.num_no_shows += 1; + self.last_tranche_with_no_show = max(tranche, self.last_tranche_with_no_show); + } + + /// Marks an assignment sent. + fn mark_sent_assignment(&mut self, tranche: u32) { + if self.sent_assignment < self.needed_approvals { + self.set_max_tranche(tranche); + } + + self.sent_assignment += 1; + } + + /// Tells if a message in this tranche should be a no-show. + fn should_no_show(&self, tranche: u32) -> bool { + (self.num_no_shows < self.max_no_shows && self.last_tranche_with_no_show < tranche) || + (tranche == 0 && self.num_no_shows == 0 && self.max_no_shows > 0) + } +} + +/// Test state that is pre-generated and loaded from a file that matches the fingerprint +/// of the TestConfiguration. +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +struct GeneratedState { + /// All assignments and approvals + all_messages: Option>, + /// The first slot in the test. + initial_slot: Slot, +} + +/// Approval test state used by all mock subsystems to be able to answer messages emitted +/// by the approval-voting and approval-distribution-subystems. +/// +/// This gets cloned across all mock subsystems, so if there is any information that gets +/// updated between subsystems, they would have to be wrapped in Arc's. +#[derive(Clone)] +pub struct ApprovalTestState { + /// The main test configuration + configuration: TestConfiguration, + /// The specific test configurations passed when starting the benchmark. + options: ApprovalsOptions, + /// The list of blocks used for testing. + blocks: Vec, + /// The babe epoch used during testing. + babe_epoch: BabeEpoch, + /// The pre-generated state. + generated_state: GeneratedState, + /// The test authorities + test_authorities: TestAuthorities, + /// Last approved block number. + last_approved_block: Arc, + /// Total sent messages from peers to node + total_sent_messages_to_node: Arc, + /// Total sent messages from test node to other peers + total_sent_messages_from_node: Arc, + /// Total unique sent messages. + total_unique_messages: Arc, + /// Approval voting metrics. + approval_voting_metrics: ApprovalVotingMetrics, + /// The delta ticks from the tick the messages were generated to the the time we start this + /// message. + delta_tick_from_generated: Arc, +} + +impl ApprovalTestState { + /// Build a new `ApprovalTestState` object out of the configurations passed when the benchmark + /// was tested. + fn new( + configuration: &TestConfiguration, + options: ApprovalsOptions, + dependencies: &TestEnvironmentDependencies, + ) -> Self { + let test_authorities = configuration.generate_authorities(); + let start = Instant::now(); + + let messages_path = PeerMessagesGenerator::generate_messages_if_needed( + configuration, + &test_authorities, + &options, + &dependencies.task_manager.spawn_handle(), + ); + + let mut messages_file = + fs::OpenOptions::new().read(true).open(messages_path.as_path()).unwrap(); + let mut messages_bytes = Vec::::with_capacity(2000000); + + messages_file + .read_to_end(&mut messages_bytes) + .expect("Could not initialize list of messages"); + let generated_state: GeneratedState = + Decode::decode(&mut messages_bytes.as_slice()).expect("Could not decode messages"); + + gum::info!( + "It took {:?} ms to load {:?} unique messages", + start.elapsed().as_millis(), + generated_state.all_messages.as_ref().map(|val| val.len()).unwrap_or_default() + ); + + let babe_epoch = + generate_babe_epoch(generated_state.initial_slot, test_authorities.clone()); + let blocks = Self::generate_blocks_information( + configuration, + &babe_epoch, + generated_state.initial_slot, + ); + + let state = ApprovalTestState { + blocks, + babe_epoch: babe_epoch.clone(), + generated_state, + test_authorities, + last_approved_block: Arc::new(AtomicU32::new(0)), + total_sent_messages_to_node: Arc::new(AtomicU64::new(0)), + total_sent_messages_from_node: Arc::new(AtomicU64::new(0)), + total_unique_messages: Arc::new(AtomicU64::new(0)), + options, + approval_voting_metrics: ApprovalVotingMetrics::try_register(&dependencies.registry) + .unwrap(), + delta_tick_from_generated: Arc::new(AtomicU64::new(630720000)), + configuration: configuration.clone(), + }; + + gum::info!("Built testing state"); + + state + } + + /// Generates the blocks and the information about the blocks that will be used + /// to drive this test. + fn generate_blocks_information( + configuration: &TestConfiguration, + babe_epoch: &BabeEpoch, + initial_slot: Slot, + ) -> Vec { + let mut per_block_heads: Vec = Vec::new(); + let mut prev_candidates = 0; + for block_number in 1..=configuration.num_blocks { + let block_hash = Hash::repeat_byte(block_number as u8); + let parent_hash = + per_block_heads.last().map(|val| val.hash).unwrap_or(Hash::repeat_byte(0xde)); + let slot_for_block = initial_slot + (block_number as u64 - 1); + + let header = make_header(parent_hash, slot_for_block, block_number as u32); + + let unsafe_vrf = approval::v1::babe_unsafe_vrf_info(&header) + .expect("Can not continue without vrf generator"); + let relay_vrf_story = unsafe_vrf + .compute_randomness( + &babe_epoch.authorities, + &babe_epoch.randomness, + babe_epoch.epoch_index, + ) + .expect("Can not continue without vrf story"); + let block_info = BlockTestData { + slot: slot_for_block, + block_number: block_number as BlockNumber, + hash: block_hash, + header, + candidates: helpers::make_candidates( + block_hash, + block_number as BlockNumber, + configuration.n_cores as u32, + configuration.n_cores as u32, + ), + relay_vrf_story, + approved: Arc::new(AtomicBool::new(false)), + total_candidates_before: prev_candidates, + votes: Arc::new( + (0..configuration.n_validators) + .map(|_| { + (0..configuration.n_cores).map(|_| AtomicBool::new(false)).collect_vec() + }) + .collect_vec(), + ), + }; + prev_candidates += block_info.candidates.len() as u64; + per_block_heads.push(block_info) + } + per_block_heads + } + + /// Starts the generation of messages(Assignments & Approvals) needed for approving blocks. + async fn start_message_production( + &mut self, + network_emulator: &NetworkEmulatorHandle, + overseer_handle: OverseerHandleReal, + env: &TestEnvironment, + registry: Registry, + ) -> oneshot::Receiver<()> { + gum::info!(target: LOG_TARGET, "Start assignments/approvals production"); + + let (producer_tx, producer_rx) = oneshot::channel(); + let peer_message_source = PeerMessageProducer { + network: network_emulator.clone(), + overseer_handle: overseer_handle.clone(), + state: self.clone(), + options: self.options.clone(), + notify_done: producer_tx, + registry, + }; + + peer_message_source + .produce_messages(env, self.generated_state.all_messages.take().unwrap()); + producer_rx + } + + // Generates a ChainApiState used for driving MockChainApi + fn build_chain_api_state(&self) -> ChainApiState { + ChainApiState { + block_headers: self + .blocks + .iter() + .map(|block| (block.hash, block.header.clone())) + .collect(), + } + } + + // Builds a map with the list of candidate events per-block. + fn candidate_events_by_block(&self) -> HashMap> { + self.blocks.iter().map(|block| (block.hash, block.candidates.clone())).collect() + } + + // Builds a map with the list of candidate hashes per-block. + fn candidate_hashes_by_block(&self) -> HashMap> { + self.blocks + .iter() + .map(|block| { + ( + block.hash, + block + .candidates + .iter() + .map(|candidate_event| match candidate_event { + CandidateEvent::CandidateBacked(_, _, _, _) => todo!(), + CandidateEvent::CandidateIncluded(receipt, _, _, _) => receipt.clone(), + CandidateEvent::CandidateTimedOut(_, _, _) => todo!(), + }) + .collect_vec(), + ) + }) + .collect() + } +} + +impl ApprovalTestState { + /// Returns test data for the given hash + fn get_info_by_hash(&self, requested_hash: Hash) -> &BlockTestData { + self.blocks + .iter() + .find(|block| block.hash == requested_hash) + .expect("Mocks should not use unknown hashes") + } + + /// Returns test data for the given slot + fn get_info_by_slot(&self, slot: Slot) -> Option<&BlockTestData> { + self.blocks.iter().find(|block| block.slot == slot) + } +} + +impl HandleNetworkMessage for ApprovalTestState { + fn handle( + &self, + _message: crate::core::network::NetworkMessage, + _node_sender: &mut futures::channel::mpsc::UnboundedSender< + crate::core::network::NetworkMessage, + >, + ) -> Option { + self.total_sent_messages_from_node + .as_ref() + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + None + } +} + +/// A generator of messages coming from a given Peer/Validator +struct PeerMessageProducer { + /// The state state used to know what messages to generate. + state: ApprovalTestState, + /// Configuration options, passed at the beginning of the test. + options: ApprovalsOptions, + /// A reference to the network emulator + network: NetworkEmulatorHandle, + /// A handle to the overseer, used for sending messages to the node + /// under test. + overseer_handle: OverseerHandleReal, + /// Channel for producer to notify main loop it finished sending + /// all messages and they have been processed. + notify_done: oneshot::Sender<()>, + /// The metrics registry. + registry: Registry, +} + +impl PeerMessageProducer { + /// Generates messages by spawning a blocking task in the background which begins creating + /// the assignments/approvals and peer view changes at the begining of each block. + fn produce_messages( + mut self, + env: &TestEnvironment, + all_messages: Vec, + ) { + env.spawn_blocking("produce-messages", async move { + let mut initialized_blocks = HashSet::new(); + let mut per_candidate_data: HashMap<(Hash, CandidateIndex), CandidateTestData> = + self.initialize_candidates_test_data(); + let mut skipped_messages: Vec = Vec::new(); + let mut re_process_skipped = false; + + let system_clock = + PastSystemClock::new(SystemClock {}, self.state.delta_tick_from_generated.clone()); + let mut all_messages = all_messages.into_iter().peekable(); + + while all_messages.peek().is_some() { + let current_slot = + tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now()); + let block_to_initialize = self + .state + .blocks + .iter() + .filter(|block_info| { + block_info.slot <= current_slot && + !initialized_blocks.contains(&block_info.hash) + }) + .cloned() + .collect_vec(); + for block_info in block_to_initialize { + if !TestEnvironment::metric_lower_than( + &self.registry, + "polkadot_parachain_imported_candidates_total", + (block_info.total_candidates_before + block_info.candidates.len() as u64 - + 1) as f64, + ) { + initialized_blocks.insert(block_info.hash); + self.initialize_block(&block_info).await; + } + } + + let mut maybe_need_skip = if re_process_skipped { + skipped_messages.clone().into_iter().peekable() + } else { + vec![].into_iter().peekable() + }; + + let progressing_iterator = if !re_process_skipped { + &mut all_messages + } else { + re_process_skipped = false; + skipped_messages.clear(); + &mut maybe_need_skip + }; + + while progressing_iterator + .peek() + .map(|bundle| { + self.time_to_process_message( + bundle, + current_slot, + &initialized_blocks, + &system_clock, + &per_candidate_data, + ) + }) + .unwrap_or_default() + { + let bundle = progressing_iterator.next().unwrap(); + re_process_skipped = self.process_message( + bundle, + &mut per_candidate_data, + &mut skipped_messages, + ) || re_process_skipped; + } + // Sleep, so that we don't busy wait in this loop when don't have anything to send. + sleep(Duration::from_millis(50)).await; + } + + gum::info!( + "All messages sent max_tranche {:?} last_tranche_with_no_show {:?}", + per_candidate_data.values().map(|data| data.max_tranche).max(), + per_candidate_data.values().map(|data| data.last_tranche_with_no_show).max() + ); + sleep(Duration::from_secs(6)).await; + // Send an empty GetApprovalSignatures as the last message + // so when the approval-distribution answered to it, we know it doesn't have anything + // else to process. + let (tx, rx) = oneshot::channel(); + let msg = ApprovalDistributionMessage::GetApprovalSignatures(HashSet::new(), tx); + self.send_overseer_message( + AllMessages::ApprovalDistribution(msg), + ValidatorIndex(0), + None, + ) + .await; + rx.await.expect("Failed to get signatures"); + self.notify_done.send(()).expect("Failed to notify main loop"); + gum::info!("All messages processed "); + }); + } + + // Processes a single message bundle and queue the messages to be sent by the peers that would + // send the message in our simulation. + pub fn process_message( + &mut self, + bundle: test_message::MessagesBundle, + per_candidate_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + skipped_messages: &mut Vec, + ) -> bool { + let mut reprocess_skipped = false; + let block_info = self + .state + .get_info_by_hash(bundle.assignments.first().unwrap().block_hash) + .clone(); + + if bundle.should_send(per_candidate_data, &self.options) { + bundle.record_sent_assignment(per_candidate_data); + + let assignments = bundle.assignments.clone(); + + for message in bundle.assignments.into_iter().chain(bundle.approvals.into_iter()) { + if message.no_show_if_required(&assignments, per_candidate_data) { + reprocess_skipped = true; + continue; + } else { + message.record_vote(&block_info); + } + self.state + .total_unique_messages + .as_ref() + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + for (peer, messages) in + message.clone().split_by_peer_id(&self.state.test_authorities) + { + for message in messages { + self.state + .total_sent_messages_to_node + .as_ref() + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + self.queue_message_from_peer(message, peer.0) + } + } + } + } else if !block_info.approved.load(std::sync::atomic::Ordering::SeqCst) && + self.options.num_no_shows_per_candidate > 0 + { + skipped_messages.push(bundle); + } + reprocess_skipped + } + + // Tells if it is the time to process a message. + pub fn time_to_process_message( + &self, + bundle: &MessagesBundle, + current_slot: Slot, + initialized_blocks: &HashSet, + system_clock: &PastSystemClock, + per_candidate_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + let block_info = + self.state.get_info_by_hash(bundle.assignments.first().unwrap().block_hash); + let tranche_now = system_clock.tranche_now(SLOT_DURATION_MILLIS, block_info.slot); + + Self::is_past_tranche( + bundle, + tranche_now, + current_slot, + block_info, + initialized_blocks.contains(&block_info.hash), + ) || !bundle.should_send(per_candidate_data, &self.options) + } + + // Tells if the tranche where the bundle should be sent has passed. + pub fn is_past_tranche( + bundle: &MessagesBundle, + tranche_now: u32, + current_slot: Slot, + block_info: &BlockTestData, + block_initialized: bool, + ) -> bool { + bundle.tranche_to_send() <= tranche_now && + current_slot >= block_info.slot && + block_initialized + } + + // Queue message to be sent by validator `sent_by` + fn queue_message_from_peer(&mut self, message: TestMessageInfo, sent_by: ValidatorIndex) { + let peer_authority_id = self + .state + .test_authorities + .validator_authority_id + .get(sent_by.0 as usize) + .expect("We can't handle unknown peers") + .clone(); + + self.network + .send_message_from_peer( + &peer_authority_id, + protocol_v3::ValidationProtocol::ApprovalDistribution(message.msg).into(), + ) + .unwrap_or_else(|_| panic!("Network should be up and running {:?}", sent_by)); + } + + // Queues a message to be sent by the peer identified by the `sent_by` value. + async fn send_overseer_message( + &mut self, + message: AllMessages, + _sent_by: ValidatorIndex, + _latency: Option, + ) { + self.overseer_handle + .send_msg(message, LOG_TARGET) + .timeout(MAX_TIME_OF_FLIGHT) + .await + .unwrap_or_else(|| { + panic!("{} ms maximum time of flight breached", MAX_TIME_OF_FLIGHT.as_millis()) + }); + } + + // Sends the messages needed by approval-distribution and approval-voting for processing a + // message. E.g: PeerViewChange. + async fn initialize_block(&mut self, block_info: &BlockTestData) { + gum::info!("Initialize block {:?}", block_info.hash); + let (tx, rx) = oneshot::channel(); + self.overseer_handle.wait_for_activation(block_info.hash, tx).await; + + rx.await + .expect("We should not fail waiting for block to be activated") + .expect("We should not fail waiting for block to be activated"); + + for validator in 1..self.state.test_authorities.validator_authority_id.len() as u32 { + let peer_id = self.state.test_authorities.peer_ids.get(validator as usize).unwrap(); + let validator = ValidatorIndex(validator); + let view_update = generate_peer_view_change_for(block_info.hash, *peer_id); + + self.send_overseer_message(view_update, validator, None).await; + } + } + + // Initializes the candidates test data. This is used for bookeeping if more assignments and + // approvals would be needed. + fn initialize_candidates_test_data( + &self, + ) -> HashMap<(Hash, CandidateIndex), CandidateTestData> { + let mut per_candidate_data: HashMap<(Hash, CandidateIndex), CandidateTestData> = + HashMap::new(); + for block_info in self.state.blocks.iter() { + for (candidate_index, _) in block_info.candidates.iter().enumerate() { + per_candidate_data.insert( + (block_info.hash, candidate_index as CandidateIndex), + CandidateTestData { + max_no_shows: self.options.num_no_shows_per_candidate, + last_tranche_with_no_show: 0, + sent_assignment: 0, + num_no_shows: 0, + max_tranche: 0, + needed_approvals: self.state.configuration.needed_approvals as u32, + }, + ); + } + } + per_candidate_data + } +} + +/// Helper function to build an overseer with the real implementation for `ApprovalDistribution` and +/// `ApprovalVoting` subystems and mock subsytems for all others. +fn build_overseer( + state: &ApprovalTestState, + network: &NetworkEmulatorHandle, + config: &TestConfiguration, + dependencies: &TestEnvironmentDependencies, + network_interface: &NetworkInterface, + network_receiver: NetworkInterfaceReceiver, +) -> (Overseer, AlwaysSupportsParachains>, OverseerHandleReal) { + let overseer_connector = OverseerConnector::with_event_capacity(6400000); + + let spawn_task_handle = dependencies.task_manager.spawn_handle(); + + let db = kvdb_memorydb::create(NUM_COLUMNS); + let db: polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter = + polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]); + let keystore = LocalKeystore::in_memory(); + + let system_clock = + PastSystemClock::new(SystemClock {}, state.delta_tick_from_generated.clone()); + let approval_voting = ApprovalVotingSubsystem::with_config_and_clock( + TEST_CONFIG, + Arc::new(db), + Arc::new(keystore), + Box::new(TestSyncOracle {}), + state.approval_voting_metrics.clone(), + Box::new(system_clock.clone()), + ); + + let approval_distribution = + ApprovalDistribution::new(Metrics::register(Some(&dependencies.registry)).unwrap()); + let mock_chain_api = MockChainApi::new(state.build_chain_api_state()); + let mock_chain_selection = MockChainSelection { state: state.clone(), clock: system_clock }; + let mock_runtime_api = MockRuntimeApi::new( + config.clone(), + state.test_authorities.clone(), + state.candidate_hashes_by_block(), + state.candidate_events_by_block(), + Some(state.babe_epoch.clone()), + 1, + ); + let mock_tx_bridge = MockNetworkBridgeTx::new( + network.clone(), + network_interface.subsystem_sender(), + state.test_authorities.clone(), + ); + let mock_rx_bridge = MockNetworkBridgeRx::new(network_receiver, None); + let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); + let dummy = dummy_builder!(spawn_task_handle, overseer_metrics) + .replace_approval_distribution(|_| approval_distribution) + .replace_approval_voting(|_| approval_voting) + .replace_chain_api(|_| mock_chain_api) + .replace_chain_selection(|_| mock_chain_selection) + .replace_runtime_api(|_| mock_runtime_api) + .replace_network_bridge_tx(|_| mock_tx_bridge) + .replace_network_bridge_rx(|_| mock_rx_bridge); + + let (overseer, raw_handle) = + dummy.build_with_connector(overseer_connector).expect("Should not fail"); + + let overseer_handle = OverseerHandleReal::new(raw_handle); + (overseer, overseer_handle) +} + +/// Takes a test configuration and uses it to creates the `TestEnvironment`. +pub fn prepare_test( + config: TestConfiguration, + options: ApprovalsOptions, +) -> (TestEnvironment, ApprovalTestState) { + prepare_test_inner(config, TestEnvironmentDependencies::default(), options) +} + +/// Build the test environment for an Approval benchmark. +fn prepare_test_inner( + config: TestConfiguration, + dependencies: TestEnvironmentDependencies, + options: ApprovalsOptions, +) -> (TestEnvironment, ApprovalTestState) { + gum::info!("Prepare test state"); + let state = ApprovalTestState::new(&config, options, &dependencies); + + gum::info!("Build network emulator"); + + let (network, network_interface, network_receiver) = + new_network(&config, &dependencies, &state.test_authorities, vec![Arc::new(state.clone())]); + + gum::info!("Build overseer"); + + let (overseer, overseer_handle) = build_overseer( + &state, + &network, + &config, + &dependencies, + &network_interface, + network_receiver, + ); + + ( + TestEnvironment::new( + dependencies, + config, + network, + overseer, + overseer_handle, + state.test_authorities.clone(), + ), + state, + ) +} + +pub async fn bench_approvals( + benchmark_name: &str, + env: &mut TestEnvironment, + mut state: ApprovalTestState, +) -> BenchmarkUsage { + let producer_rx = state + .start_message_production( + env.network(), + env.overseer_handle().clone(), + env, + env.registry().clone(), + ) + .await; + bench_approvals_run(benchmark_name, env, state, producer_rx).await +} + +/// Runs the approval benchmark. +pub async fn bench_approvals_run( + benchmark_name: &str, + env: &mut TestEnvironment, + state: ApprovalTestState, + producer_rx: oneshot::Receiver<()>, +) -> BenchmarkUsage { + let config = env.config().clone(); + + env.metrics().set_n_validators(config.n_validators); + env.metrics().set_n_cores(config.n_cores); + + // First create the initialization messages that make sure that then node under + // tests receives notifications about the topology used and the connected peers. + let mut initialization_messages = env.network().generate_peer_connected(); + initialization_messages.extend(generate_new_session_topology( + &state.test_authorities, + ValidatorIndex(NODE_UNDER_TEST), + )); + for message in initialization_messages { + env.send_message(message).await; + } + + let start_marker = Instant::now(); + let real_clock = SystemClock {}; + state.delta_tick_from_generated.store( + real_clock.tick_now() - + slot_number_to_tick(SLOT_DURATION_MILLIS, state.generated_state.initial_slot), + std::sync::atomic::Ordering::SeqCst, + ); + let system_clock = PastSystemClock::new(real_clock, state.delta_tick_from_generated.clone()); + + for block_num in 0..env.config().num_blocks { + let mut current_slot = tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now()); + + // Wait untill the time arrieves at the first slot under test. + while current_slot < state.generated_state.initial_slot { + sleep(Duration::from_millis(5)).await; + current_slot = tick_to_slot_number(SLOT_DURATION_MILLIS, system_clock.tick_now()); + } + + gum::info!(target: LOG_TARGET, "Current block {}/{}", block_num + 1, env.config().num_blocks); + env.metrics().set_current_block(block_num); + let block_start_ts = Instant::now(); + + if let Some(block_info) = state.get_info_by_slot(current_slot) { + env.import_block(new_block_import_info(block_info.hash, block_info.block_number)) + .await; + } + + let block_time = Instant::now().sub(block_start_ts).as_millis() as u64; + env.metrics().set_block_time(block_time); + gum::info!("Block time {}", format!("{:?}ms", block_time).cyan()); + + system_clock + .wait(slot_number_to_tick(SLOT_DURATION_MILLIS, current_slot + 1)) + .await; + } + + // Wait for all blocks to be approved before exiting. + // This is an invariant of the benchmark, if this does not happen something went teribbly wrong. + while state.last_approved_block.load(std::sync::atomic::Ordering::SeqCst) < + env.config().num_blocks as u32 + { + gum::info!( + "Waiting for all blocks to be approved current approved {:} num_sent {:} num_unique {:}", + state.last_approved_block.load(std::sync::atomic::Ordering::SeqCst), + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst), + state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst) + ); + tokio::time::sleep(Duration::from_secs(6)).await; + } + + gum::info!("Awaiting producer to signal done"); + + producer_rx.await.expect("Failed to receive done from message producer"); + + gum::info!("Awaiting polkadot_parachain_subsystem_bounded_received to tells us the messages have been processed"); + let at_least_messages = + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize; + env.wait_until_metric( + "polkadot_parachain_subsystem_bounded_received", + Some(("subsystem_name", "approval-distribution-subsystem")), + |value| { + gum::info!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); + value >= at_least_messages as f64 + }, + ) + .await; + gum::info!("Requesting approval votes ms"); + + for info in &state.blocks { + for (index, candidates) in info.candidates.iter().enumerate() { + match candidates { + CandidateEvent::CandidateBacked(_, _, _, _) => todo!(), + CandidateEvent::CandidateIncluded(receipt_fetch, _head, _, _) => { + let (tx, rx) = oneshot::channel(); + + let msg = ApprovalVotingMessage::GetApprovalSignaturesForCandidate( + receipt_fetch.hash(), + tx, + ); + env.send_message(AllMessages::ApprovalVoting(msg)).await; + + let result = rx.await.unwrap(); + + for (validator, _) in result.iter() { + info.votes + .get(validator.0 as usize) + .unwrap() + .get(index) + .unwrap() + .store(false, std::sync::atomic::Ordering::SeqCst); + } + }, + + CandidateEvent::CandidateTimedOut(_, _, _) => todo!(), + }; + } + } + + gum::info!("Awaiting polkadot_parachain_subsystem_bounded_received to tells us the messages have been processed"); + let at_least_messages = + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst) as usize; + env.wait_until_metric( + "polkadot_parachain_subsystem_bounded_received", + Some(("subsystem_name", "approval-distribution-subsystem")), + |value| { + gum::info!(target: LOG_TARGET, ?value, ?at_least_messages, "Waiting metric"); + value >= at_least_messages as f64 + }, + ) + .await; + + for state in &state.blocks { + for (validator, votes) in state + .votes + .as_ref() + .iter() + .enumerate() + .filter(|(validator, _)| *validator != NODE_UNDER_TEST as usize) + { + for (index, candidate) in votes.iter().enumerate() { + assert_eq!( + ( + validator, + index, + candidate.load(std::sync::atomic::Ordering::SeqCst), + state.hash + ), + (validator, index, false, state.hash) + ); + } + } + } + + env.stop().await; + + let duration: u128 = start_marker.elapsed().as_millis(); + gum::info!( + "All blocks processed in {} total_sent_messages_to_node {} total_sent_messages_from_node {} num_unique_messages {}", + format!("{:?}ms", duration).cyan(), + state.total_sent_messages_to_node.load(std::sync::atomic::Ordering::SeqCst), + state.total_sent_messages_from_node.load(std::sync::atomic::Ordering::SeqCst), + state.total_unique_messages.load(std::sync::atomic::Ordering::SeqCst) + ); + + env.collect_resource_usage(benchmark_name, &["approval-distribution", "approval-voting"]) +} diff --git a/polkadot/node/subsystem-bench/src/approval/test_message.rs b/polkadot/node/subsystem-bench/src/approval/test_message.rs new file mode 100644 index 0000000000000000000000000000000000000000..8aaabc3426c803563bdb2ed8455eed8a0061484e --- /dev/null +++ b/polkadot/node/subsystem-bench/src/approval/test_message.rs @@ -0,0 +1,306 @@ +// Copyright (C) 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 crate::{ + approval::{BlockTestData, CandidateTestData}, + core::configuration::TestAuthorities, + ApprovalsOptions, +}; +use itertools::Itertools; +use parity_scale_codec::{Decode, Encode}; +use polkadot_node_network_protocol::v3 as protocol_v3; +use polkadot_primitives::{CandidateIndex, Hash, ValidatorIndex}; +use sc_network::PeerId; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +pub struct TestMessageInfo { + /// The actual message + pub msg: protocol_v3::ApprovalDistributionMessage, + /// The list of peers that would sends this message in a real topology. + /// It includes both the peers that would send the message because of the topology + /// or because of randomly chosing so. + pub sent_by: Vec, + /// The tranche at which this message should be sent. + pub tranche: u32, + /// The block hash this message refers to. + pub block_hash: Hash, +} + +impl std::hash::Hash for TestMessageInfo { + fn hash(&self, state: &mut H) { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { + for (assignment, candidates) in assignments { + (assignment.block_hash, assignment.validator).hash(state); + candidates.hash(state); + } + }, + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => { + for approval in approvals { + (approval.block_hash, approval.validator).hash(state); + approval.candidate_indices.hash(state); + } + }, + }; + } +} + +#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] +/// A list of messages that depend of each-other, approvals cover one of the assignments and +/// vice-versa. +pub struct MessagesBundle { + pub assignments: Vec, + pub approvals: Vec, +} + +impl MessagesBundle { + /// The tranche when this bundle can be sent correctly, so no assignments or approvals will be + /// from the future. + pub fn tranche_to_send(&self) -> u32 { + self.assignments + .iter() + .chain(self.approvals.iter()) + .max_by(|a, b| a.tranche.cmp(&b.tranche)) + .unwrap() + .tranche + } + + /// The min tranche in the bundle. + pub fn min_tranche(&self) -> u32 { + self.assignments + .iter() + .chain(self.approvals.iter()) + .min_by(|a, b| a.tranche.cmp(&b.tranche)) + .unwrap() + .tranche + } + + /// Tells if the bundle is needed for sending. + /// We either send it because we need more assignments and approvals to approve the candidates + /// or because we configured the test to send messages untill a given tranche. + pub fn should_send( + &self, + candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + options: &ApprovalsOptions, + ) -> bool { + self.needed_for_approval(candidates_test_data) || + (!options.stop_when_approved && + self.min_tranche() <= options.last_considered_tranche) + } + + /// Tells if the bundle is needed because we need more messages to approve the candidates. + pub fn needed_for_approval( + &self, + candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + self.assignments + .iter() + .any(|message| message.needed_for_approval(candidates_test_data)) + } + + /// Mark the assignments in the bundle as sent. + pub fn record_sent_assignment( + &self, + candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) { + self.assignments + .iter() + .for_each(|assignment| assignment.record_sent_assignment(candidates_test_data)); + } +} + +impl TestMessageInfo { + /// Tells if the message is an approval. + fn is_approval(&self) -> bool { + match self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(_) => false, + protocol_v3::ApprovalDistributionMessage::Approvals(_) => true, + } + } + + /// Records an approval. + /// We use this to check after all messages have been processed that we didn't loose any + /// message. + pub fn record_vote(&self, state: &BlockTestData) { + if self.is_approval() { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(_) => todo!(), + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => + for approval in approvals { + for candidate_index in approval.candidate_indices.iter_ones() { + state + .votes + .get(approval.validator.0 as usize) + .unwrap() + .get(candidate_index) + .unwrap() + .store(true, std::sync::atomic::Ordering::SeqCst); + } + }, + } + } + } + + /// Mark the assignments in the message as sent. + pub fn record_sent_assignment( + &self, + candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => { + for (assignment, candidate_indices) in assignments { + for candidate_index in candidate_indices.iter_ones() { + let candidate_test_data = candidates_test_data + .get_mut(&(assignment.block_hash, candidate_index as CandidateIndex)) + .unwrap(); + candidate_test_data.mark_sent_assignment(self.tranche) + } + } + }, + protocol_v3::ApprovalDistributionMessage::Approvals(_approvals) => todo!(), + } + } + + /// Returns a list of candidates indicies in this message + pub fn candidate_indices(&self) -> HashSet { + let mut unique_candidate_indicies = HashSet::new(); + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => + for (_assignment, candidate_indices) in assignments { + for candidate_index in candidate_indices.iter_ones() { + unique_candidate_indicies.insert(candidate_index); + } + }, + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => + for approval in approvals { + for candidate_index in approval.candidate_indices.iter_ones() { + unique_candidate_indicies.insert(candidate_index); + } + }, + } + unique_candidate_indicies + } + + /// Marks this message as no-shows if the number of configured no-shows is above the registered + /// no-shows. + /// Returns true if the message is a no-show. + pub fn no_show_if_required( + &self, + assignments: &[TestMessageInfo], + candidates_test_data: &mut HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + let mut should_no_show = false; + if self.is_approval() { + let covered_candidates = assignments + .iter() + .map(|assignment| (assignment, assignment.candidate_indices())) + .collect_vec(); + + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(_) => todo!(), + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => { + assert_eq!(approvals.len(), 1); + + for approval in approvals { + should_no_show = should_no_show || + approval.candidate_indices.iter_ones().all(|candidate_index| { + let candidate_test_data = candidates_test_data + .get_mut(&( + approval.block_hash, + candidate_index as CandidateIndex, + )) + .unwrap(); + let assignment = covered_candidates + .iter() + .find(|(_assignment, candidates)| { + candidates.contains(&candidate_index) + }) + .unwrap(); + candidate_test_data.should_no_show(assignment.0.tranche) + }); + + if should_no_show { + for candidate_index in approval.candidate_indices.iter_ones() { + let candidate_test_data = candidates_test_data + .get_mut(&( + approval.block_hash, + candidate_index as CandidateIndex, + )) + .unwrap(); + let assignment = covered_candidates + .iter() + .find(|(_assignment, candidates)| { + candidates.contains(&candidate_index) + }) + .unwrap(); + candidate_test_data.record_no_show(assignment.0.tranche) + } + } + } + }, + } + } + should_no_show + } + + /// Tells if a message is needed for approval + pub fn needed_for_approval( + &self, + candidates_test_data: &HashMap<(Hash, CandidateIndex), CandidateTestData>, + ) -> bool { + match &self.msg { + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) => + assignments.iter().any(|(assignment, candidate_indices)| { + candidate_indices.iter_ones().any(|candidate_index| { + candidates_test_data + .get(&(assignment.block_hash, candidate_index as CandidateIndex)) + .map(|data| data.should_send_tranche(self.tranche)) + .unwrap_or_default() + }) + }), + protocol_v3::ApprovalDistributionMessage::Approvals(approvals) => + approvals.iter().any(|approval| { + approval.candidate_indices.iter_ones().any(|candidate_index| { + candidates_test_data + .get(&(approval.block_hash, candidate_index as CandidateIndex)) + .map(|data| data.should_send_tranche(self.tranche)) + .unwrap_or_default() + }) + }), + } + } + + /// Splits a message into multiple messages based on what peers should send this message. + /// It build a HashMap of messages that should be sent by each peer. + pub fn split_by_peer_id( + self, + authorities: &TestAuthorities, + ) -> HashMap<(ValidatorIndex, PeerId), Vec> { + let mut result: HashMap<(ValidatorIndex, PeerId), Vec> = HashMap::new(); + + for validator_index in &self.sent_by { + let peer = authorities.peer_ids.get(validator_index.0 as usize).unwrap(); + result.entry((*validator_index, *peer)).or_default().push(TestMessageInfo { + msg: self.msg.clone(), + sent_by: Default::default(), + tranche: self.tranche, + block_hash: self.block_hash, + }); + } + result + } +} diff --git a/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs b/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs index 18ea2f72891fd7f9839449bb2e4495dc5b60621b..261dbd0376c732fb4688e6251f32e21d3ec8d4d4 100644 --- a/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs +++ b/polkadot/node/subsystem-bench/src/availability/av_store_helpers.rs @@ -14,14 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use super::*; - +use crate::core::{environment::TestEnvironmentDependencies, mock::TestSyncOracle}; +use polkadot_node_core_av_store::{AvailabilityStoreSubsystem, Config}; use polkadot_node_metrics::metrics::Metrics; - -use polkadot_node_core_av_store::Config; use polkadot_node_subsystem_util::database::Database; - -use polkadot_node_core_av_store::AvailabilityStoreSubsystem; +use std::sync::Arc; mod columns { pub const DATA: u32 = 0; @@ -31,22 +28,10 @@ mod columns { const TEST_CONFIG: Config = Config { col_data: columns::DATA, col_meta: columns::META }; -struct DumbOracle; - -impl sp_consensus::SyncOracle for DumbOracle { - fn is_major_syncing(&self) -> bool { - false - } - - fn is_offline(&self) -> bool { - unimplemented!("oh no!") - } -} - pub fn new_av_store(dependencies: &TestEnvironmentDependencies) -> AvailabilityStoreSubsystem { let metrics = Metrics::try_register(&dependencies.registry).unwrap(); - AvailabilityStoreSubsystem::new(test_store(), TEST_CONFIG, Box::new(DumbOracle), metrics) + AvailabilityStoreSubsystem::new(test_store(), TEST_CONFIG, Box::new(TestSyncOracle {}), metrics) } fn test_store() -> Arc { diff --git a/polkadot/node/subsystem-bench/src/availability/mod.rs b/polkadot/node/subsystem-bench/src/availability/mod.rs index f9892efb3c68ce31763f7caa1e4e4bb76a2b938a..ad9a17ff8f47a1a3dc7e8e8e990cb0b1f7f262f6 100644 --- a/polkadot/node/subsystem-bench/src/availability/mod.rs +++ b/polkadot/node/subsystem-bench/src/availability/mod.rs @@ -13,71 +13,73 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::{core::mock::ChainApiState, TestEnvironment}; + +use crate::{ + core::{ + configuration::TestConfiguration, + environment::{BenchmarkUsage, TestEnvironmentDependencies}, + mock::{ + av_store, + av_store::MockAvailabilityStore, + chain_api::{ChainApiState, MockChainApi}, + dummy_builder, + network_bridge::{self, MockNetworkBridgeRx, MockNetworkBridgeTx}, + runtime_api, + runtime_api::MockRuntimeApi, + AlwaysSupportsParachains, + }, + network::new_network, + }, + TestEnvironment, TestObjective, GENESIS_HASH, +}; use av_store::NetworkAvailabilityState; +use av_store_helpers::new_av_store; use bitvec::bitvec; use colored::Colorize; +use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt}; use itertools::Itertools; +use parity_scale_codec::Encode; use polkadot_availability_bitfield_distribution::BitfieldDistribution; -use polkadot_node_core_av_store::AvailabilityStoreSubsystem; -use polkadot_node_subsystem::{Overseer, OverseerConnector, SpawnGlue}; -use polkadot_node_subsystem_types::{ - messages::{AvailabilityStoreMessage, NetworkBridgeEvent}, - Span, -}; -use polkadot_overseer::Handle as OverseerHandle; -use sc_network::{request_responses::ProtocolConfig, PeerId}; -use sp_core::H256; -use std::{collections::HashMap, iter::Cycle, ops::Sub, sync::Arc, time::Instant}; - -use av_store_helpers::new_av_store; -use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt}; use polkadot_availability_distribution::{ AvailabilityDistributionSubsystem, IncomingRequestReceivers, }; -use polkadot_node_metrics::metrics::Metrics; - use polkadot_availability_recovery::AvailabilityRecoverySubsystem; -use polkadot_node_primitives::{AvailableData, ErasureChunk}; - -use crate::GENESIS_HASH; -use parity_scale_codec::Encode; +use polkadot_node_core_av_store::AvailabilityStoreSubsystem; +use polkadot_node_metrics::metrics::Metrics; use polkadot_node_network_protocol::{ request_response::{v1::ChunkFetchingRequest, IncomingRequest, ReqProtocolNames}, OurView, Versioned, VersionedValidationProtocol, }; -use sc_network::request_responses::IncomingRequest as RawIncomingRequest; - -use polkadot_node_primitives::{BlockData, PoV}; -use polkadot_node_subsystem::messages::{AllMessages, AvailabilityRecoveryMessage}; - -use crate::core::{ - environment::TestEnvironmentDependencies, - mock::{ - av_store, - network_bridge::{self, MockNetworkBridgeRx, MockNetworkBridgeTx}, - runtime_api, MockAvailabilityStore, MockChainApi, MockRuntimeApi, - }, +use polkadot_node_primitives::{AvailableData, BlockData, ErasureChunk, PoV}; +use polkadot_node_subsystem::{ + messages::{AllMessages, AvailabilityRecoveryMessage}, + Overseer, OverseerConnector, SpawnGlue, }; - -use super::core::{configuration::TestConfiguration, mock::dummy_builder, network::*}; - -const LOG_TARGET: &str = "subsystem-bench::availability"; - -use super::{cli::TestObjective, core::mock::AlwaysSupportsParachains}; use polkadot_node_subsystem_test_helpers::{ derive_erasure_chunks_with_proofs_and_root, mock::new_block_import_info, }; +use polkadot_node_subsystem_types::{ + messages::{AvailabilityStoreMessage, NetworkBridgeEvent}, + Span, +}; +use polkadot_overseer::{metrics::Metrics as OverseerMetrics, Handle as OverseerHandle}; use polkadot_primitives::{ AvailabilityBitfield, BlockNumber, CandidateHash, CandidateReceipt, GroupIndex, Hash, HeadData, Header, PersistedValidationData, Signed, SigningContext, ValidatorIndex, }; use polkadot_primitives_test_helpers::{dummy_candidate_receipt, dummy_hash}; +use sc_network::{ + request_responses::{IncomingRequest as RawIncomingRequest, ProtocolConfig}, + PeerId, +}; use sc_service::SpawnTaskHandle; +use sp_core::H256; +use std::{collections::HashMap, iter::Cycle, ops::Sub, sync::Arc, time::Instant}; mod av_store_helpers; -mod cli; -pub use cli::{DataAvailabilityReadOptions, NetworkEmulation}; +pub(crate) mod cli; + +const LOG_TARGET: &str = "subsystem-bench::availability"; fn build_overseer_for_availability_read( spawn_task_handle: SpawnTaskHandle, @@ -85,9 +87,12 @@ fn build_overseer_for_availability_read( av_store: MockAvailabilityStore, network_bridge: (MockNetworkBridgeTx, MockNetworkBridgeRx), availability_recovery: AvailabilityRecoverySubsystem, + dependencies: &TestEnvironmentDependencies, ) -> (Overseer, AlwaysSupportsParachains>, OverseerHandle) { let overseer_connector = OverseerConnector::with_event_capacity(64000); - let dummy = dummy_builder!(spawn_task_handle); + let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); + + let dummy = dummy_builder!(spawn_task_handle, overseer_metrics); let builder = dummy .replace_runtime_api(|_| runtime_api) .replace_availability_store(|_| av_store) @@ -101,6 +106,7 @@ fn build_overseer_for_availability_read( (overseer, OverseerHandle::new(raw_handle)) } +#[allow(clippy::too_many_arguments)] fn build_overseer_for_availability_write( spawn_task_handle: SpawnTaskHandle, runtime_api: MockRuntimeApi, @@ -109,9 +115,12 @@ fn build_overseer_for_availability_write( chain_api: MockChainApi, availability_store: AvailabilityStoreSubsystem, bitfield_distribution: BitfieldDistribution, + dependencies: &TestEnvironmentDependencies, ) -> (Overseer, AlwaysSupportsParachains>, OverseerHandle) { let overseer_connector = OverseerConnector::with_event_capacity(64000); - let dummy = dummy_builder!(spawn_task_handle); + let overseer_metrics = OverseerMetrics::try_register(&dependencies.registry).unwrap(); + + let dummy = dummy_builder!(spawn_task_handle, overseer_metrics); let builder = dummy .replace_runtime_api(|_| runtime_api) .replace_availability_store(|_| availability_store) @@ -171,6 +180,9 @@ fn prepare_test_inner( config.clone(), test_authorities.clone(), candidate_hashes, + Default::default(), + Default::default(), + 0, ); let availability_state = NetworkAvailabilityState { @@ -198,6 +210,7 @@ fn prepare_test_inner( let network_bridge_tx = network_bridge::MockNetworkBridgeTx::new( network.clone(), network_interface.subsystem_sender(), + test_authorities.clone(), ); let network_bridge_rx = @@ -231,6 +244,7 @@ fn prepare_test_inner( av_store, (network_bridge_tx, network_bridge_rx), subsystem, + &dependencies, ) }, TestObjective::DataAvailabilityWrite => { @@ -240,7 +254,7 @@ fn prepare_test_inner( Metrics::try_register(&dependencies.registry).unwrap(), ); - let block_headers = (0..=config.num_blocks) + let block_headers = (1..=config.num_blocks) .map(|block_number| { ( Hash::repeat_byte(block_number as u8), @@ -267,6 +281,7 @@ fn prepare_test_inner( chain_api, new_av_store(&dependencies), bitfield_distribution, + &dependencies, ) }, _ => { @@ -417,7 +432,11 @@ impl TestState { } } -pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: TestState) { +pub async fn benchmark_availability_read( + benchmark_name: &str, + env: &mut TestEnvironment, + mut state: TestState, +) -> BenchmarkUsage { let config = env.config().clone(); env.import_block(new_block_import_info(Hash::repeat_byte(1), 1)).await; @@ -477,12 +496,15 @@ pub async fn benchmark_availability_read(env: &mut TestEnvironment, mut state: T format!("{} ms", test_start.elapsed().as_millis() / env.config().num_blocks as u128).red() ); - env.display_network_usage(); - env.display_cpu_usage(&["availability-recovery"]); env.stop().await; + env.collect_resource_usage(benchmark_name, &["availability-recovery"]) } -pub async fn benchmark_availability_write(env: &mut TestEnvironment, mut state: TestState) { +pub async fn benchmark_availability_write( + benchmark_name: &str, + env: &mut TestEnvironment, + mut state: TestState, +) -> BenchmarkUsage { let config = env.config().clone(); env.metrics().set_n_validators(config.n_validators); @@ -614,9 +636,10 @@ pub async fn benchmark_availability_write(env: &mut TestEnvironment, mut state: ); // Wait for all bitfields to be processed. - env.wait_until_metric_eq( + env.wait_until_metric( "polkadot_parachain_received_availabilty_bitfields_total", - config.connected_count() * block_num, + None, + |value| value == (config.connected_count() * block_num) as f64, ) .await; @@ -634,15 +657,11 @@ pub async fn benchmark_availability_write(env: &mut TestEnvironment, mut state: format!("{} ms", test_start.elapsed().as_millis() / env.config().num_blocks as u128).red() ); - env.display_network_usage(); - - env.display_cpu_usage(&[ - "availability-distribution", - "bitfield-distribution", - "availability-store", - ]); - env.stop().await; + env.collect_resource_usage( + benchmark_name, + &["availability-distribution", "bitfield-distribution", "availability-store"], + ) } pub fn peer_bitfield_message_v2( diff --git a/polkadot/node/subsystem-bench/src/cli.rs b/polkadot/node/subsystem-bench/src/cli.rs deleted file mode 100644 index 7213713eb6baa38537bf32b1c1b7867c0e0ad846..0000000000000000000000000000000000000000 --- a/polkadot/node/subsystem-bench/src/cli.rs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 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::availability::DataAvailabilityReadOptions; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize, clap::Parser)] -#[clap(rename_all = "kebab-case")] -#[allow(missing_docs)] -pub struct TestSequenceOptions { - #[clap(short, long, ignore_case = true)] - pub path: String, -} - -/// Supported test objectives -#[derive(Debug, Clone, clap::Parser, Serialize, Deserialize)] -#[command(rename_all = "kebab-case")] -pub enum TestObjective { - /// Benchmark availability recovery strategies. - DataAvailabilityRead(DataAvailabilityReadOptions), - /// Benchmark availability and bitfield distribution. - DataAvailabilityWrite, - /// Run a test sequence specified in a file - TestSequence(TestSequenceOptions), -} - -#[derive(Debug, clap::Parser)] -#[clap(rename_all = "kebab-case")] -#[allow(missing_docs)] -pub struct StandardTestOptions { - #[clap(long, ignore_case = true, default_value_t = 100)] - /// Number of cores to fetch availability for. - pub n_cores: usize, - - #[clap(long, ignore_case = true, default_value_t = 500)] - /// Number of validators to fetch chunks from. - pub n_validators: usize, - - #[clap(long, ignore_case = true, default_value_t = 5120)] - /// The minimum pov size in KiB - pub min_pov_size: usize, - - #[clap(long, ignore_case = true, default_value_t = 5120)] - /// The maximum pov size bytes - pub max_pov_size: usize, - - #[clap(short, long, ignore_case = true, default_value_t = 1)] - /// The number of blocks the test is going to run. - pub num_blocks: usize, -} diff --git a/polkadot/node/subsystem-bench/src/core/configuration.rs b/polkadot/node/subsystem-bench/src/core/configuration.rs index 66da8a1db45d7bcd008c3bf5c0f3693594ac35b5..00be2a86b173a61465d7610d75df1abbe09363f0 100644 --- a/polkadot/node/subsystem-bench/src/core/configuration.rs +++ b/polkadot/node/subsystem-bench/src/core/configuration.rs @@ -13,18 +13,18 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -// + //! Test configuration definition and helpers. -use super::*; -use keyring::Keyring; -use std::path::Path; -pub use crate::cli::TestObjective; -use polkadot_primitives::{AuthorityDiscoveryId, ValidatorId}; +use crate::{core::keyring::Keyring, TestObjective}; +use itertools::Itertools; +use polkadot_primitives::{AssignmentId, AuthorityDiscoveryId, ValidatorId}; use rand::thread_rng; use rand_distr::{Distribution, Normal, Uniform}; - +use sc_network::PeerId; use serde::{Deserialize, Serialize}; +use sp_consensus_babe::AuthorityId; +use std::{collections::HashMap, path::Path}; pub fn random_pov_size(min_pov_size: usize, max_pov_size: usize) -> usize { random_uniform_sample(min_pov_size, max_pov_size) @@ -65,6 +65,25 @@ fn default_backing_group_size() -> usize { 5 } +// Default needed approvals +fn default_needed_approvals() -> usize { + 30 +} + +fn default_zeroth_delay_tranche_width() -> usize { + 0 +} +fn default_relay_vrf_modulo_samples() -> usize { + 6 +} + +fn default_n_delay_tranches() -> usize { + 89 +} +fn default_no_show_slots() -> usize { + 3 +} + /// The test input parameters #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TestConfiguration { @@ -74,6 +93,17 @@ pub struct TestConfiguration { pub n_validators: usize, /// Number of cores pub n_cores: usize, + /// The number of needed votes to approve a candidate. + #[serde(default = "default_needed_approvals")] + pub needed_approvals: usize, + #[serde(default = "default_zeroth_delay_tranche_width")] + pub zeroth_delay_tranche_width: usize, + #[serde(default = "default_relay_vrf_modulo_samples")] + pub relay_vrf_modulo_samples: usize, + #[serde(default = "default_n_delay_tranches")] + pub n_delay_tranches: usize, + #[serde(default = "default_no_show_slots")] + pub no_show_slots: usize, /// Maximum backing group size #[serde(default = "default_backing_group_size")] pub max_validators_per_core: usize, @@ -139,6 +169,11 @@ pub struct TestAuthorities { pub keyring: Keyring, pub validator_public: Vec, pub validator_authority_id: Vec, + pub validator_babe_id: Vec, + pub validator_assignment_id: Vec, + pub key_seeds: Vec, + pub peer_ids: Vec, + pub peer_id_to_authority: HashMap, } impl TestConfiguration { @@ -162,91 +197,44 @@ impl TestConfiguration { pub fn generate_authorities(&self) -> TestAuthorities { let keyring = Keyring::default(); - let keys = (0..self.n_validators) - .map(|peer_index| keyring.sr25519_new(format!("Node{}", peer_index))) + let key_seeds = (0..self.n_validators) + .map(|peer_index| format!("//Node{}", peer_index)) + .collect_vec(); + + let keys = key_seeds + .iter() + .map(|seed| keyring.sr25519_new(seed.as_str())) .collect::>(); - // Generate `AuthorityDiscoveryId`` for each peer + // Generate keys and peers ids in each of the format needed by the tests. let validator_public: Vec = keys.iter().map(|key| (*key).into()).collect::>(); let validator_authority_id: Vec = keys.iter().map(|key| (*key).into()).collect::>(); - TestAuthorities { keyring, validator_public, validator_authority_id } - } - - /// An unconstrained standard configuration matching Polkadot/Kusama - pub fn ideal_network( - objective: TestObjective, - num_blocks: usize, - n_validators: usize, - n_cores: usize, - min_pov_size: usize, - max_pov_size: usize, - ) -> TestConfiguration { - Self { - objective, - n_cores, - n_validators, - max_validators_per_core: 5, - pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size), - bandwidth: 50 * 1024 * 1024, - peer_bandwidth: 50 * 1024 * 1024, - // No latency - latency: None, - num_blocks, - min_pov_size, - max_pov_size, - connectivity: 100, - } - } - - pub fn healthy_network( - objective: TestObjective, - num_blocks: usize, - n_validators: usize, - n_cores: usize, - min_pov_size: usize, - max_pov_size: usize, - ) -> TestConfiguration { - Self { - objective, - n_cores, - n_validators, - max_validators_per_core: 5, - pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size), - bandwidth: 50 * 1024 * 1024, - peer_bandwidth: 50 * 1024 * 1024, - latency: Some(PeerLatency { mean_latency_ms: 50, std_dev: 12.5 }), - num_blocks, - min_pov_size, - max_pov_size, - connectivity: 95, - } - } + let validator_babe_id: Vec = + keys.iter().map(|key| (*key).into()).collect::>(); - pub fn degraded_network( - objective: TestObjective, - num_blocks: usize, - n_validators: usize, - n_cores: usize, - min_pov_size: usize, - max_pov_size: usize, - ) -> TestConfiguration { - Self { - objective, - n_cores, - n_validators, - max_validators_per_core: 5, - pov_sizes: generate_pov_sizes(n_cores, min_pov_size, max_pov_size), - bandwidth: 50 * 1024 * 1024, - peer_bandwidth: 50 * 1024 * 1024, - latency: Some(PeerLatency { mean_latency_ms: 150, std_dev: 40.0 }), - num_blocks, - min_pov_size, - max_pov_size, - connectivity: 67, + let validator_assignment_id: Vec = + keys.iter().map(|key| (*key).into()).collect::>(); + let peer_ids: Vec = keys.iter().map(|_| PeerId::random()).collect::>(); + + let peer_id_to_authority = peer_ids + .iter() + .zip(validator_authority_id.iter()) + .map(|(peer_id, authorithy_id)| (*peer_id, authorithy_id.clone())) + .collect(); + + TestAuthorities { + keyring, + validator_public, + validator_authority_id, + peer_ids, + validator_babe_id, + validator_assignment_id, + key_seeds, + peer_id_to_authority, } } } diff --git a/polkadot/node/subsystem-bench/src/core/display.rs b/polkadot/node/subsystem-bench/src/core/display.rs index bca82d7b90ae9290b5bd969233f56f2df854f116..13a349382e2fd776f41685fb133b96f1058e7b43 100644 --- a/polkadot/node/subsystem-bench/src/core/display.rs +++ b/polkadot/node/subsystem-bench/src/core/display.rs @@ -13,12 +13,13 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -// + //! Display implementations and helper methods for parsing prometheus metrics //! to a format that can be displayed in the CLI. //! //! Currently histogram buckets are skipped. -use super::{configuration::TestConfiguration, LOG_TARGET}; + +use crate::{TestConfiguration, LOG_TARGET}; use colored::Colorize; use prometheus::{ proto::{MetricFamily, MetricType}, @@ -26,7 +27,7 @@ use prometheus::{ }; use std::fmt::Display; -#[derive(Default)] +#[derive(Default, Debug)] pub struct MetricCollection(Vec); impl From> for MetricCollection { @@ -49,6 +50,11 @@ impl MetricCollection { .sum() } + /// Tells if entries in bucket metric is lower than `value` + pub fn metric_lower_than(&self, metric_name: &str, value: f64) -> bool { + self.sum_by(metric_name) < value + } + pub fn subset_with_label_value(&self, label_name: &str, label_value: &str) -> MetricCollection { self.0 .iter() @@ -163,7 +169,7 @@ pub fn parse_metrics(registry: &Registry) -> MetricCollection { name: h_name, label_names, label_values, - value: h.get_sample_sum(), + value: h.get_sample_count() as f64, }); }, MetricType::SUMMARY => { diff --git a/polkadot/node/subsystem-bench/src/core/environment.rs b/polkadot/node/subsystem-bench/src/core/environment.rs index b6846316430b0da0fcc7d2c21cb93fc6f2e582e2..ca4c41cf45f99851023e1c6899b3d1f5e7913eb8 100644 --- a/polkadot/node/subsystem-bench/src/core/environment.rs +++ b/polkadot/node/subsystem-bench/src/core/environment.rs @@ -13,28 +13,31 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . + //! Test environment implementation + use crate::{ - core::{mock::AlwaysSupportsParachains, network::NetworkEmulatorHandle}, + core::{ + configuration::TestAuthorities, mock::AlwaysSupportsParachains, + network::NetworkEmulatorHandle, + }, TestConfiguration, }; use colored::Colorize; use core::time::Duration; use futures::{Future, FutureExt}; -use polkadot_overseer::{BlockInfo, Handle as OverseerHandle}; - use polkadot_node_subsystem::{messages::AllMessages, Overseer, SpawnGlue, TimeoutExt}; use polkadot_node_subsystem_types::Hash; use polkadot_node_subsystem_util::metrics::prometheus::{ self, Gauge, Histogram, PrometheusError, Registry, U64, }; - +use polkadot_overseer::{BlockInfo, Handle as OverseerHandle}; use sc_service::{SpawnTaskHandle, TaskManager}; +use serde::{Deserialize, Serialize}; use std::net::{Ipv4Addr, SocketAddr}; use tokio::runtime::Handle; const LOG_TARGET: &str = "subsystem-bench::environment"; -use super::configuration::TestAuthorities; /// Test environment/configuration metrics #[derive(Clone)] @@ -243,6 +246,11 @@ impl TestEnvironment { &self.network } + /// Returns a reference to the overseer handle. + pub fn overseer_handle(&self) -> &OverseerHandle { + &self.overseer_handle + } + /// Returns the Prometheus registry. pub fn registry(&self) -> &Registry { &self.dependencies.registry @@ -311,74 +319,133 @@ impl TestEnvironment { self.overseer_handle.stop().await; } - /// Blocks until `metric_name` == `value` - pub async fn wait_until_metric_eq(&self, metric_name: &str, value: usize) { - let value = value as f64; + /// Tells if entries in bucket metric is lower than `value` + pub fn metric_lower_than(registry: &Registry, metric_name: &str, value: f64) -> bool { + let test_metrics = super::display::parse_metrics(registry); + test_metrics.metric_lower_than(metric_name, value) + } + + /// Blocks until `metric_name` >= `value` + pub async fn wait_until_metric( + &self, + metric_name: &str, + label: Option<(&str, &str)>, + condition: impl Fn(f64) -> bool, + ) { loop { - let test_metrics = super::display::parse_metrics(self.registry()); + let test_metrics = if let Some((label_name, label_value)) = label { + super::display::parse_metrics(self.registry()) + .subset_with_label_value(label_name, label_value) + } else { + super::display::parse_metrics(self.registry()) + }; let current_value = test_metrics.sum_by(metric_name); - gum::debug!(target: LOG_TARGET, metric_name, current_value, value, "Waiting for metric"); - if current_value == value { + gum::debug!(target: LOG_TARGET, metric_name, current_value, "Waiting for metric"); + if condition(current_value) { break } - // Check value every 50ms. tokio::time::sleep(std::time::Duration::from_millis(50)).await; } } - /// Display network usage stats. - pub fn display_network_usage(&self) { - let stats = self.network().peer_stats(0); - - let total_node_received = stats.received() / 1024; - let total_node_sent = stats.sent() / 1024; - - println!( - "\nPayload bytes received from peers: {}, {}", - format!("{:.2} KiB total", total_node_received).blue(), - format!("{:.2} KiB/block", total_node_received / self.config().num_blocks) - .bright_blue() - ); + pub fn collect_resource_usage( + &self, + benchmark_name: &str, + subsystems_under_test: &[&str], + ) -> BenchmarkUsage { + BenchmarkUsage { + benchmark_name: benchmark_name.to_string(), + network_usage: self.network_usage(), + cpu_usage: self.cpu_usage(subsystems_under_test), + } + } - println!( - "Payload bytes sent to peers: {}, {}", - format!("{:.2} KiB total", total_node_sent).blue(), - format!("{:.2} KiB/block", total_node_sent / self.config().num_blocks).bright_blue() - ); + fn network_usage(&self) -> Vec { + let stats = self.network().peer_stats(0); + let total_node_received = (stats.received() / 1024) as f64; + let total_node_sent = (stats.sent() / 1024) as f64; + let num_blocks = self.config().num_blocks as f64; + + vec![ + ResourceUsage { + resource_name: "Received from peers".to_string(), + total: total_node_received, + per_block: total_node_received / num_blocks, + }, + ResourceUsage { + resource_name: "Sent to peers".to_string(), + total: total_node_sent, + per_block: total_node_sent / num_blocks, + }, + ] } - /// Print CPU usage stats in the CLI. - pub fn display_cpu_usage(&self, subsystems_under_test: &[&str]) { + fn cpu_usage(&self, subsystems_under_test: &[&str]) -> Vec { let test_metrics = super::display::parse_metrics(self.registry()); + let mut usage = vec![]; + let num_blocks = self.config().num_blocks as f64; for subsystem in subsystems_under_test.iter() { let subsystem_cpu_metrics = test_metrics.subset_with_label_value("task_group", subsystem); let total_cpu = subsystem_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum"); - println!( - "{} CPU usage {}", - subsystem.to_string().bright_green(), - format!("{:.3}s", total_cpu).bright_purple() - ); - println!( - "{} CPU usage per block {}", - subsystem.to_string().bright_green(), - format!("{:.3}s", total_cpu / self.config().num_blocks as f64).bright_purple() - ); + usage.push(ResourceUsage { + resource_name: subsystem.to_string(), + total: total_cpu, + per_block: total_cpu / num_blocks, + }); } let test_env_cpu_metrics = test_metrics.subset_with_label_value("task_group", "test-environment"); let total_cpu = test_env_cpu_metrics.sum_by("substrate_tasks_polling_duration_sum"); - println!( - "Total test environment CPU usage {}", - format!("{:.3}s", total_cpu).bright_purple() - ); - println!( - "Test environment CPU usage per block {}", - format!("{:.3}s", total_cpu / self.config().num_blocks as f64).bright_purple() + + usage.push(ResourceUsage { + resource_name: "Test environment".to_string(), + total: total_cpu, + per_block: total_cpu / num_blocks, + }); + + usage + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct BenchmarkUsage { + benchmark_name: String, + network_usage: Vec, + cpu_usage: Vec, +} + +impl std::fmt::Display for BenchmarkUsage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "\n{}\n\n{}\n{}\n\n{}\n{}\n", + self.benchmark_name.purple(), + format!("{:<32}{:>12}{:>12}", "Network usage, KiB", "total", "per block").blue(), + self.network_usage + .iter() + .map(|v| v.to_string()) + .collect::>() + .join("\n"), + format!("{:<32}{:>12}{:>12}", "CPU usage in seconds", "total", "per block").blue(), + self.cpu_usage.iter().map(|v| v.to_string()).collect::>().join("\n") ) } } + +#[derive(Debug, Serialize, Deserialize)] +pub struct ResourceUsage { + resource_name: String, + total: f64, + per_block: f64, +} + +impl std::fmt::Display for ResourceUsage { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{:<32}{:>12.3}{:>12.3}", self.resource_name.cyan(), self.total, self.per_block) + } +} diff --git a/polkadot/node/subsystem-bench/src/core/keyring.rs b/polkadot/node/subsystem-bench/src/core/keyring.rs index 66c7229847c3a34b8ecfda6339ced5ee8b086d28..c290d30b46fbe1a2a0db04155d8460c288fd98ff 100644 --- a/polkadot/node/subsystem-bench/src/core/keyring.rs +++ b/polkadot/node/subsystem-bench/src/core/keyring.rs @@ -34,13 +34,17 @@ impl Default for Keyring { } impl Keyring { - pub fn sr25519_new(&self, name: String) -> Public { + pub fn sr25519_new(&self, seed: &str) -> Public { self.keystore - .sr25519_generate_new(ValidatorId::ID, Some(&format!("//{}", name))) + .sr25519_generate_new(ValidatorId::ID, Some(seed)) .expect("Insert key into keystore") } pub fn keystore(&self) -> Arc { self.keystore.clone() } + + pub fn keystore_ref(&self) -> &LocalKeystore { + self.keystore.as_ref() + } } diff --git a/polkadot/node/subsystem-bench/src/core/mock/av_store.rs b/polkadot/node/subsystem-bench/src/core/mock/av_store.rs index 76609ab5dba607865d28e5402f9bff5571ea403d..0a7725c91e0421e79a20b2ec77d8ade72ab05714 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/av_store.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/av_store.rs @@ -13,30 +13,24 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! + //! A generic av store subsystem mockup suitable to be used in benchmarks. +use crate::core::network::{HandleNetworkMessage, NetworkMessage}; +use futures::{channel::oneshot, FutureExt}; use parity_scale_codec::Encode; use polkadot_node_network_protocol::request_response::{ v1::{AvailableDataFetchingResponse, ChunkFetchingResponse, ChunkResponse}, Requests, }; -use polkadot_primitives::CandidateHash; -use sc_network::ProtocolName; - -use std::collections::HashMap; - -use futures::{channel::oneshot, FutureExt}; - use polkadot_node_primitives::{AvailableData, ErasureChunk}; - use polkadot_node_subsystem::{ messages::AvailabilityStoreMessage, overseer, SpawnedSubsystem, SubsystemError, }; - use polkadot_node_subsystem_types::OverseerSignal; - -use crate::core::network::{HandleNetworkMessage, NetworkMessage}; +use polkadot_primitives::CandidateHash; +use sc_network::ProtocolName; +use std::collections::HashMap; pub struct AvailabilityStoreState { candidate_hashes: HashMap, diff --git a/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs b/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs index 008d8eef106a0f0b4ebe1f266bd850956e698e23..bee15c3cefdfbda061a1313db6c631e52fd91c12 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/chain_api.rs @@ -13,20 +13,19 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! -//! A generic runtime api subsystem mockup suitable to be used in benchmarks. -use polkadot_primitives::Header; +//! A generic runtime api subsystem mockup suitable to be used in benchmarks. +use futures::FutureExt; +use itertools::Itertools; use polkadot_node_subsystem::{ messages::ChainApiMessage, overseer, SpawnedSubsystem, SubsystemError, }; use polkadot_node_subsystem_types::OverseerSignal; +use polkadot_primitives::Header; use sp_core::H256; use std::collections::HashMap; -use futures::FutureExt; - const LOG_TARGET: &str = "subsystem-bench::chain-api-mock"; /// State used to respond to `BlockHeader` requests. @@ -38,6 +37,12 @@ pub struct MockChainApi { state: ChainApiState, } +impl ChainApiState { + fn get_header_by_number(&self, requested_number: u32) -> Option<&Header> { + self.block_headers.values().find(|header| header.number == requested_number) + } +} + impl MockChainApi { pub fn new(state: ChainApiState) -> MockChainApi { Self { state } @@ -77,9 +82,44 @@ impl MockChainApi { .expect("Relay chain block hashes are known"), ))); }, - ChainApiMessage::Ancestors { hash: _hash, k: _k, response_channel } => { - // For our purposes, no ancestors is fine. - let _ = response_channel.send(Ok(Vec::new())); + ChainApiMessage::FinalizedBlockNumber(val) => { + val.send(Ok(0)).unwrap(); + }, + ChainApiMessage::FinalizedBlockHash(requested_number, sender) => { + let hash = self + .state + .get_header_by_number(requested_number) + .expect("Unknow block number") + .hash(); + sender.send(Ok(Some(hash))).unwrap(); + }, + ChainApiMessage::BlockNumber(requested_hash, sender) => { + sender + .send(Ok(Some( + self.state + .block_headers + .get(&requested_hash) + .expect("Unknown block hash") + .number, + ))) + .unwrap(); + }, + ChainApiMessage::Ancestors { hash, k: _, response_channel } => { + let block_number = self + .state + .block_headers + .get(&hash) + .expect("Unknown block hash") + .number; + let ancestors = self + .state + .block_headers + .iter() + .filter(|(_, header)| header.number < block_number) + .sorted_by(|a, b| a.1.number.cmp(&b.1.number)) + .map(|(hash, _)| *hash) + .collect_vec(); + response_channel.send(Ok(ancestors)).unwrap(); }, _ => { unimplemented!("Unexpected chain-api message") diff --git a/polkadot/node/subsystem-bench/src/core/mock/dummy.rs b/polkadot/node/subsystem-bench/src/core/mock/dummy.rs index a0a908750c5181619fe836cdbf86c42ba8b99596..8783b35f1c04a9c59bf415340689b1a9ae29ee6a 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/dummy.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/dummy.rs @@ -13,10 +13,11 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . + //! Dummy subsystem mocks. -use paste::paste; use futures::FutureExt; +use paste::paste; use polkadot_node_subsystem::{overseer, SpawnedSubsystem, SubsystemError}; use std::time::Duration; use tokio::time::sleep; diff --git a/polkadot/node/subsystem-bench/src/core/mock/mod.rs b/polkadot/node/subsystem-bench/src/core/mock/mod.rs index b67c6611e8cd34ba3afe058c8a8a28774d291036..46fdeb196c011587dd081851e9e1f31863a0530e 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/mod.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/mod.rs @@ -16,6 +16,7 @@ use polkadot_node_subsystem::HeadSupportsParachains; use polkadot_node_subsystem_types::Hash; +use sp_consensus::SyncOracle; pub mod av_store; pub mod chain_api; @@ -23,11 +24,8 @@ pub mod dummy; pub mod network_bridge; pub mod runtime_api; -pub use av_store::*; -pub use chain_api::*; -pub use runtime_api::*; - pub struct AlwaysSupportsParachains {} + #[async_trait::async_trait] impl HeadSupportsParachains for AlwaysSupportsParachains { async fn head_supports_parachains(&self, _head: &Hash) -> bool { @@ -37,8 +35,8 @@ impl HeadSupportsParachains for AlwaysSupportsParachains { // An orchestra with dummy subsystems macro_rules! dummy_builder { - ($spawn_task_handle: ident) => {{ - use super::core::mock::dummy::*; + ($spawn_task_handle: ident, $metrics: ident) => {{ + use $crate::core::mock::dummy::*; // Initialize a mock overseer. // All subsystem except approval_voting and approval_distribution are mock subsystems. @@ -69,10 +67,22 @@ macro_rules! dummy_builder { .activation_external_listeners(Default::default()) .span_per_active_leaf(Default::default()) .active_leaves(Default::default()) - .metrics(Default::default()) + .metrics($metrics) .supports_parachains(AlwaysSupportsParachains {}) .spawner(SpawnGlue($spawn_task_handle)) }}; } - pub(crate) use dummy_builder; + +#[derive(Clone)] +pub struct TestSyncOracle {} + +impl SyncOracle for TestSyncOracle { + fn is_major_syncing(&self) -> bool { + false + } + + fn is_offline(&self) -> bool { + unimplemented!("not used by subsystem benchmarks") + } +} diff --git a/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs b/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs index a2be853ef8d51a9d6a34164d7be4392ae2214472..4682c7ec79ae3532858365b135636afefcb5400f 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/network_bridge.rs @@ -13,26 +13,24 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! + //! Mocked `network-bridge` subsystems that uses a `NetworkInterface` to access //! the emulated network. -use futures::{channel::mpsc::UnboundedSender, FutureExt, StreamExt}; -use polkadot_node_subsystem_types::{ - messages::{BitfieldDistributionMessage, NetworkBridgeEvent}, - OverseerSignal, -}; - -use sc_network::{request_responses::ProtocolConfig, PeerId, RequestFailure}; +use crate::core::{ + configuration::TestAuthorities, + network::{NetworkEmulatorHandle, NetworkInterfaceReceiver, NetworkMessage, RequestExt}, +}; +use futures::{channel::mpsc::UnboundedSender, FutureExt, StreamExt}; +use polkadot_node_network_protocol::Versioned; use polkadot_node_subsystem::{ messages::NetworkBridgeTxMessage, overseer, SpawnedSubsystem, SubsystemError, }; - -use polkadot_node_network_protocol::Versioned; - -use crate::core::network::{ - NetworkEmulatorHandle, NetworkInterfaceReceiver, NetworkMessage, RequestExt, +use polkadot_node_subsystem_types::{ + messages::{ApprovalDistributionMessage, BitfieldDistributionMessage, NetworkBridgeEvent}, + OverseerSignal, }; +use sc_network::{request_responses::ProtocolConfig, RequestFailure}; const LOG_TARGET: &str = "subsystem-bench::network-bridge"; const CHUNK_REQ_PROTOCOL_NAME_V1: &str = @@ -44,6 +42,8 @@ pub struct MockNetworkBridgeTx { network: NetworkEmulatorHandle, /// A channel to the network interface, to_network_interface: UnboundedSender, + /// Test authorithies + test_authorithies: TestAuthorities, } /// A mock of the network bridge tx subsystem. @@ -58,8 +58,9 @@ impl MockNetworkBridgeTx { pub fn new( network: NetworkEmulatorHandle, to_network_interface: UnboundedSender, + test_authorithies: TestAuthorities, ) -> MockNetworkBridgeTx { - Self { network, to_network_interface } + Self { network, to_network_interface, test_authorithies } } } @@ -126,9 +127,21 @@ impl MockNetworkBridgeTx { NetworkBridgeTxMessage::ReportPeer(_) => { // ingore rep changes }, - _ => { - unimplemented!("Unexpected network bridge message") + NetworkBridgeTxMessage::SendValidationMessage(peers, message) => { + for peer in peers { + self.to_network_interface + .unbounded_send(NetworkMessage::MessageFromNode( + self.test_authorithies + .peer_id_to_authority + .get(&peer) + .unwrap() + .clone(), + message.clone(), + )) + .expect("Should not fail"); + } }, + _ => unimplemented!("Unexpected network bridge message"), }, } } @@ -145,16 +158,23 @@ impl MockNetworkBridgeRx { maybe_peer_message = from_network_interface.next() => { if let Some(message) = maybe_peer_message { match message { - NetworkMessage::MessageFromPeer(message) => match message { + NetworkMessage::MessageFromPeer(peer_id, message) => match message { Versioned::V2( polkadot_node_network_protocol::v2::ValidationProtocol::BitfieldDistribution( bitfield, ), ) => { ctx.send_message( - BitfieldDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(PeerId::random(), polkadot_node_network_protocol::Versioned::V2(bitfield))) + BitfieldDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V2(bitfield))) ).await; }, + Versioned::V3( + polkadot_node_network_protocol::v3::ValidationProtocol::ApprovalDistribution(msg) + ) => { + ctx.send_message( + ApprovalDistributionMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerMessage(peer_id, polkadot_node_network_protocol::Versioned::V3(msg))) + ).await; + } _ => { unimplemented!("We only talk v2 network protocol") }, diff --git a/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs b/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs index caefe068efff50bc24ae27e1bad86dc1e1da1c42..0dd76efcbaf0de5cdbd456d0bff91658ad0ac737 100644 --- a/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs +++ b/polkadot/node/subsystem-bench/src/core/mock/runtime_api.rs @@ -13,33 +13,39 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! -//! A generic runtime api subsystem mockup suitable to be used in benchmarks. -use polkadot_primitives::{ - CandidateReceipt, CoreState, GroupIndex, IndexedVec, OccupiedCore, SessionInfo, ValidatorIndex, -}; +//! A generic runtime api subsystem mockup suitable to be used in benchmarks. +use crate::core::configuration::{TestAuthorities, TestConfiguration}; use bitvec::prelude::BitVec; +use futures::FutureExt; +use itertools::Itertools; use polkadot_node_subsystem::{ messages::{RuntimeApiMessage, RuntimeApiRequest}, overseer, SpawnedSubsystem, SubsystemError, }; use polkadot_node_subsystem_types::OverseerSignal; +use polkadot_primitives::{ + vstaging::NodeFeatures, CandidateEvent, CandidateReceipt, CoreState, GroupIndex, IndexedVec, + OccupiedCore, SessionIndex, SessionInfo, ValidatorIndex, +}; +use sp_consensus_babe::Epoch as BabeEpoch; use sp_core::H256; use std::collections::HashMap; -use crate::core::configuration::{TestAuthorities, TestConfiguration}; -use futures::FutureExt; - const LOG_TARGET: &str = "subsystem-bench::runtime-api-mock"; /// Minimal state to answer requests. pub struct RuntimeApiState { // All authorities in the test, authorities: TestAuthorities, - // Candidate + // Candidate hashes per block candidate_hashes: HashMap>, + // Included candidates per bock + included_candidates: HashMap>, + babe_epoch: Option, + // The session child index, + session_index: SessionIndex, } /// A mocked `runtime-api` subsystem. @@ -53,34 +59,57 @@ impl MockRuntimeApi { config: TestConfiguration, authorities: TestAuthorities, candidate_hashes: HashMap>, + included_candidates: HashMap>, + babe_epoch: Option, + session_index: SessionIndex, ) -> MockRuntimeApi { - Self { state: RuntimeApiState { authorities, candidate_hashes }, config } + Self { + state: RuntimeApiState { + authorities, + candidate_hashes, + included_candidates, + babe_epoch, + session_index, + }, + config, + } } fn session_info(&self) -> SessionInfo { - let all_validators = (0..self.config.n_validators) - .map(|i| ValidatorIndex(i as _)) - .collect::>(); - - let validator_groups = all_validators - .chunks(self.config.max_validators_per_core) - .map(Vec::from) - .collect::>(); - SessionInfo { - validators: self.state.authorities.validator_public.clone().into(), - discovery_keys: self.state.authorities.validator_authority_id.clone(), - validator_groups: IndexedVec::>::from(validator_groups), - assignment_keys: vec![], - n_cores: self.config.n_cores as u32, - zeroth_delay_tranche_width: 0, - relay_vrf_modulo_samples: 0, - n_delay_tranches: 0, - no_show_slots: 0, - needed_approvals: 0, - active_validator_indices: vec![], - dispute_period: 6, - random_seed: [0u8; 32], - } + session_info_for_peers(&self.config, &self.state.authorities) + } +} + +/// Generates a test session info with all passed authorities as consensus validators. +pub fn session_info_for_peers( + configuration: &TestConfiguration, + authorities: &TestAuthorities, +) -> SessionInfo { + let all_validators = (0..configuration.n_validators) + .map(|i| ValidatorIndex(i as _)) + .collect::>(); + + let validator_groups = all_validators + .chunks(configuration.max_validators_per_core) + .map(Vec::from) + .collect::>(); + + SessionInfo { + validators: authorities.validator_public.iter().cloned().collect(), + discovery_keys: authorities.validator_authority_id.to_vec(), + assignment_keys: authorities.validator_assignment_id.to_vec(), + validator_groups: IndexedVec::>::from(validator_groups), + n_cores: configuration.n_cores as u32, + needed_approvals: configuration.needed_approvals as u32, + zeroth_delay_tranche_width: configuration.zeroth_delay_tranche_width as u32, + relay_vrf_modulo_samples: configuration.relay_vrf_modulo_samples as u32, + n_delay_tranches: configuration.n_delay_tranches as u32, + no_show_slots: configuration.no_show_slots as u32, + active_validator_indices: (0..authorities.validator_authority_id.len()) + .map(|index| ValidatorIndex(index as u32)) + .collect_vec(), + dispute_period: 6, + random_seed: [0u8; 32], } } @@ -110,6 +139,13 @@ impl MockRuntimeApi { gum::debug!(target: LOG_TARGET, msg=?msg, "recv message"); match msg { + RuntimeApiMessage::Request( + request, + RuntimeApiRequest::CandidateEvents(sender), + ) => { + let candidate_events = self.state.included_candidates.get(&request); + let _ = sender.send(Ok(candidate_events.cloned().unwrap_or_default())); + }, RuntimeApiMessage::Request( _block_hash, RuntimeApiRequest::SessionInfo(_session_index, sender), @@ -123,24 +159,24 @@ impl MockRuntimeApi { let _ = sender.send(Ok(Some(Default::default()))); }, RuntimeApiMessage::Request( - _block_hash, - RuntimeApiRequest::Validators(sender), + _request, + RuntimeApiRequest::NodeFeatures(_session_index, sender), ) => { - let _ = - sender.send(Ok(self.state.authorities.validator_public.clone())); + let _ = sender.send(Ok(NodeFeatures::EMPTY)); }, RuntimeApiMessage::Request( _block_hash, - RuntimeApiRequest::CandidateEvents(sender), + RuntimeApiRequest::Validators(sender), ) => { - let _ = sender.send(Ok(Default::default())); + let _ = + sender.send(Ok(self.state.authorities.validator_public.clone())); }, RuntimeApiMessage::Request( _block_hash, RuntimeApiRequest::SessionIndexForChild(sender), ) => { // Session is always the same. - let _ = sender.send(Ok(0)); + let _ = sender.send(Ok(self.state.session_index)); }, RuntimeApiMessage::Request( block_hash, @@ -176,10 +212,14 @@ impl MockRuntimeApi { let _ = sender.send(Ok(cores)); }, RuntimeApiMessage::Request( - _block_hash, - RuntimeApiRequest::NodeFeatures(_session_index, sender), + _request, + RuntimeApiRequest::CurrentBabeEpoch(sender), ) => { - let _ = sender.send(Ok(Default::default())); + let _ = sender.send(Ok(self + .state + .babe_epoch + .clone() + .expect("Babe epoch unpopulated"))); }, // Long term TODO: implement more as needed. message => { diff --git a/polkadot/node/subsystem-bench/src/core/mod.rs b/polkadot/node/subsystem-bench/src/core/mod.rs index 282788d143b44a9a2444533f1eda756e0385c0a2..764184c5b37779efb7dc22dead338cc05ee9b942 100644 --- a/polkadot/node/subsystem-bench/src/core/mod.rs +++ b/polkadot/node/subsystem-bench/src/core/mod.rs @@ -14,11 +14,12 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -const LOG_TARGET: &str = "subsystem-bench::core"; +// The validator index that represent the node that is under test. +pub(crate) const NODE_UNDER_TEST: u32 = 0; -pub mod configuration; -pub mod display; -pub mod environment; -pub mod keyring; -pub mod mock; -pub mod network; +pub(crate) mod configuration; +pub(crate) mod display; +pub(crate) mod environment; +pub(crate) mod keyring; +pub(crate) mod mock; +pub(crate) mod network; diff --git a/polkadot/node/subsystem-bench/src/core/network.rs b/polkadot/node/subsystem-bench/src/core/network.rs index e2932bf0f51b662213b0cdf041e8409d9d590694..e9124726d7c063f15eaf7b048b291b1b2115e897 100644 --- a/polkadot/node/subsystem-bench/src/core/network.rs +++ b/polkadot/node/subsystem-bench/src/core/network.rs @@ -10,13 +10,12 @@ // 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 . -//! + //! Implements network emulation and interfaces to control and specialize //! network peer behaviour. -// + // [TestEnvironment] // [NetworkEmulatorHandle] // || @@ -34,44 +33,52 @@ // | // Subsystems under test -use crate::core::configuration::random_latency; - -use super::{ - configuration::{TestAuthorities, TestConfiguration}, +use crate::core::{ + configuration::{random_latency, TestAuthorities, TestConfiguration}, environment::TestEnvironmentDependencies, - *, + NODE_UNDER_TEST, }; use colored::Colorize; use futures::{ - channel::{mpsc, oneshot}, + channel::{ + mpsc, + mpsc::{UnboundedReceiver, UnboundedSender}, + oneshot, + }, lock::Mutex, stream::FuturesUnordered, + Future, FutureExt, StreamExt, }; - +use itertools::Itertools; use net_protocol::{ + peer_set::{ProtocolVersion, ValidationVersion}, request_response::{Recipient, Requests, ResponseSender}, - VersionedValidationProtocol, + ObservedRole, VersionedValidationProtocol, }; use parity_scale_codec::Encode; +use polkadot_node_network_protocol::{self as net_protocol, Versioned}; +use polkadot_node_subsystem_types::messages::{ApprovalDistributionMessage, NetworkBridgeEvent}; +use polkadot_node_subsystem_util::metrics::prometheus::{ + self, CounterVec, Opts, PrometheusError, Registry, +}; +use polkadot_overseer::AllMessages; use polkadot_primitives::AuthorityDiscoveryId; use prometheus_endpoint::U64; use rand::{seq::SliceRandom, thread_rng}; use sc_network::{ request_responses::{IncomingRequest, OutgoingResponse}, - RequestFailure, + PeerId, RequestFailure, }; use sc_service::SpawnTaskHandle; use std::{ collections::HashMap, sync::Arc, + task::Poll, time::{Duration, Instant}, }; -use polkadot_node_network_protocol::{self as net_protocol, Versioned}; - -use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; +const LOG_TARGET: &str = "subsystem-bench::network"; -use futures::{Future, FutureExt, StreamExt}; // An emulated node egress traffic rate_limiter. #[derive(Debug)] pub struct RateLimit { @@ -142,7 +149,7 @@ impl RateLimit { /// peer(`AuthorityDiscoveryId``). pub enum NetworkMessage { /// A gossip message from peer to node. - MessageFromPeer(VersionedValidationProtocol), + MessageFromPeer(PeerId, VersionedValidationProtocol), /// A gossip message from node to a peer. MessageFromNode(AuthorityDiscoveryId, VersionedValidationProtocol), /// A request originating from our node @@ -155,9 +162,9 @@ impl NetworkMessage { /// Returns the size of the encoded message or request pub fn size(&self) -> usize { match &self { - NetworkMessage::MessageFromPeer(Versioned::V2(message)) => message.encoded_size(), - NetworkMessage::MessageFromPeer(Versioned::V1(message)) => message.encoded_size(), - NetworkMessage::MessageFromPeer(Versioned::V3(message)) => message.encoded_size(), + NetworkMessage::MessageFromPeer(_, Versioned::V2(message)) => message.encoded_size(), + NetworkMessage::MessageFromPeer(_, Versioned::V1(message)) => message.encoded_size(), + NetworkMessage::MessageFromPeer(_, Versioned::V3(message)) => message.encoded_size(), NetworkMessage::MessageFromNode(_peer_id, Versioned::V2(message)) => message.encoded_size(), NetworkMessage::MessageFromNode(_peer_id, Versioned::V1(message)) => @@ -199,8 +206,6 @@ struct ProxiedResponse { pub result: Result, RequestFailure>, } -use std::task::Poll; - impl Future for ProxiedRequest { // The sender and result. type Output = ProxiedResponse; @@ -262,9 +267,9 @@ impl NetworkInterface { task_rx_limiter.lock().await.reap(size).await; rx_network.inc_received(size); - // To be able to apply the configured bandwidth limits for responses being sent - // over channels, we need to implement a simple proxy that allows this loop - // to receive the response and enforce the configured bandwidth before + // To be able to apply the configured bandwidth limits for responses being sent + // over channels, we need to implement a simple proxy that allows this loop + // to receive the response and enforce the configured bandwidth before // sending it to the original recipient. if let NetworkMessage::RequestFromPeer(request) = peer_message { let (response_sender, response_receiver) = oneshot::channel(); @@ -430,6 +435,7 @@ pub struct EmulatedPeerHandle { messages_tx: UnboundedSender, /// Send actions to be performed by the peer. actions_tx: UnboundedSender, + peer_id: PeerId, } impl EmulatedPeerHandle { @@ -441,7 +447,7 @@ impl EmulatedPeerHandle { /// Send a message to the node. pub fn send_message(&self, message: VersionedValidationProtocol) { self.actions_tx - .unbounded_send(NetworkMessage::MessageFromPeer(message)) + .unbounded_send(NetworkMessage::MessageFromPeer(self.peer_id, message)) .expect("Peer action channel hangup"); } @@ -613,6 +619,7 @@ pub fn new_peer( stats: Arc, to_network_interface: UnboundedSender, latency_ms: usize, + peer_id: PeerId, ) -> EmulatedPeerHandle { let (messages_tx, messages_rx) = mpsc::unbounded::(); let (actions_tx, actions_rx) = mpsc::unbounded::(); @@ -641,7 +648,7 @@ pub fn new_peer( .boxed(), ); - EmulatedPeerHandle { messages_tx, actions_tx } + EmulatedPeerHandle { messages_tx, actions_tx, peer_id } } /// Book keeping of sent and received bytes. @@ -719,6 +726,28 @@ pub struct NetworkEmulatorHandle { validator_authority_ids: HashMap, } +impl NetworkEmulatorHandle { + /// Generates peer_connected messages for all peers in `test_authorities` + pub fn generate_peer_connected(&self) -> Vec { + self.peers + .iter() + .filter(|peer| peer.is_connected()) + .map(|peer| { + let network = NetworkBridgeEvent::PeerConnected( + peer.handle().peer_id, + ObservedRole::Full, + ProtocolVersion::from(ValidationVersion::V3), + None, + ); + + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NetworkBridgeUpdate( + network, + )) + }) + .collect_vec() + } +} + /// Create a new emulated network based on `config`. /// Each emulated peer will run the specified `handlers` to process incoming messages. pub fn new_network( @@ -753,6 +782,7 @@ pub fn new_network( stats, to_network_interface.clone(), random_latency(config.latency.as_ref()), + *authorities.peer_ids.get(peer_index).unwrap(), )), ) }) @@ -760,10 +790,14 @@ pub fn new_network( let connected_count = config.connected_count(); - let (_connected, to_disconnect) = peers.partial_shuffle(&mut thread_rng(), connected_count); + let mut peers_indicies = (0..n_peers).collect_vec(); + let (_connected, to_disconnect) = + peers_indicies.partial_shuffle(&mut thread_rng(), connected_count); - for peer in to_disconnect { - peer.disconnect(); + // Node under test is always mark as disconnected. + peers[NODE_UNDER_TEST as usize].disconnect(); + for peer in to_disconnect.iter().skip(1) { + peers[*peer].disconnect(); } gum::info!(target: LOG_TARGET, "{}",format!("Network created, connected validator count {}", connected_count).bright_black()); @@ -786,6 +820,7 @@ pub fn new_network( } /// Errors that can happen when sending data to emulated peers. +#[derive(Clone, Debug)] pub enum EmulatedPeerError { NotConnected, } @@ -881,10 +916,6 @@ impl NetworkEmulatorHandle { } } -use polkadot_node_subsystem_util::metrics::prometheus::{ - self, CounterVec, Opts, PrometheusError, Registry, -}; - /// Emulated network metrics. #[derive(Clone)] pub(crate) struct Metrics { @@ -1003,9 +1034,8 @@ impl RequestExt for Requests { #[cfg(test)] mod tests { - use std::time::Instant; - use super::RateLimit; + use std::time::Instant; #[tokio::test] async fn test_expected_rate() { diff --git a/polkadot/node/subsystem-bench/src/subsystem-bench.rs b/polkadot/node/subsystem-bench/src/subsystem-bench.rs index 8633ebb703aa6eda949965d6640a0ad2cf261cdf..0803f175474e69a76640b12497acaaafa4cd5213 100644 --- a/polkadot/node/subsystem-bench/src/subsystem-bench.rs +++ b/polkadot/node/subsystem-bench/src/subsystem-bench.rs @@ -16,35 +16,33 @@ //! A tool for running subsystem benchmark tests designed for development and //! CI regression testing. -use clap::Parser; - -use colored::Colorize; +use approval::{bench_approvals, ApprovalsOptions}; +use availability::{ + cli::{DataAvailabilityReadOptions, NetworkEmulation}, + prepare_test, TestState, +}; +use clap::Parser; +use clap_num::number_range; use color_eyre::eyre; +use colored::Colorize; +use core::{ + configuration::TestConfiguration, + display::display_configuration, + environment::{TestEnvironment, GENESIS_HASH}, +}; use pyroscope::PyroscopeAgent; use pyroscope_pprofrs::{pprof_backend, PprofConfig}; - +use serde::{Deserialize, Serialize}; use std::path::Path; -pub(crate) mod availability; -pub(crate) mod cli; -pub(crate) mod core; +mod approval; +mod availability; +mod core; mod valgrind; const LOG_TARGET: &str = "subsystem-bench"; -use availability::{prepare_test, NetworkEmulation, TestState}; -use cli::TestObjective; - -use core::{ - configuration::TestConfiguration, - environment::{TestEnvironment, GENESIS_HASH}, -}; - -use clap_num::number_range; - -use crate::core::display::display_configuration; - fn le_100(s: &str) -> Result { number_range(s, 0, 100) } @@ -53,6 +51,34 @@ fn le_5000(s: &str) -> Result { number_range(s, 0, 5000) } +/// Supported test objectives +#[derive(Debug, Clone, Parser, Serialize, Deserialize)] +#[command(rename_all = "kebab-case")] +pub enum TestObjective { + /// Benchmark availability recovery strategies. + DataAvailabilityRead(DataAvailabilityReadOptions), + /// Benchmark availability and bitfield distribution. + DataAvailabilityWrite, + /// Benchmark the approval-voting and approval-distribution subsystems. + ApprovalVoting(ApprovalsOptions), + Unimplemented, +} + +impl std::fmt::Display for TestObjective { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::DataAvailabilityRead(_) => "DataAvailabilityRead", + Self::DataAvailabilityWrite => "DataAvailabilityWrite", + Self::ApprovalVoting(_) => "ApprovalVoting", + Self::Unimplemented => "Unimplemented", + } + ) + } +} + #[derive(Debug, Parser)] #[allow(missing_docs)] struct BenchCli { @@ -60,9 +86,6 @@ struct BenchCli { /// The type of network to be emulated pub network: NetworkEmulation, - #[clap(flatten)] - pub standard_configuration: cli::StandardTestOptions, - #[clap(short, long)] /// The bandwidth of emulated remote peers in KiB pub peer_bandwidth: Option, @@ -99,42 +122,16 @@ struct BenchCli { /// Enable Cache Misses Profiling with Valgrind. Linux only, Valgrind must be in the PATH pub cache_misses: bool, - #[command(subcommand)] - pub objective: cli::TestObjective, + #[clap(long, default_value_t = false)] + /// Shows the output in YAML format + pub yaml_output: bool, + + #[arg(required = true)] + /// Path to the test sequence configuration file + pub path: String, } impl BenchCli { - fn create_test_configuration(&self) -> TestConfiguration { - let configuration = &self.standard_configuration; - - match self.network { - NetworkEmulation::Healthy => TestConfiguration::healthy_network( - self.objective.clone(), - configuration.num_blocks, - configuration.n_validators, - configuration.n_cores, - configuration.min_pov_size, - configuration.max_pov_size, - ), - NetworkEmulation::Degraded => TestConfiguration::degraded_network( - self.objective.clone(), - configuration.num_blocks, - configuration.n_validators, - configuration.n_cores, - configuration.min_pov_size, - configuration.max_pov_size, - ), - NetworkEmulation::Ideal => TestConfiguration::ideal_network( - self.objective.clone(), - configuration.num_blocks, - configuration.n_validators, - configuration.n_cores, - configuration.min_pov_size, - configuration.max_pov_size, - ), - } - } - fn launch(self) -> eyre::Result<()> { let is_valgrind_running = valgrind::is_valgrind_running(); if !is_valgrind_running && self.cache_misses { @@ -151,87 +148,49 @@ impl BenchCli { None }; - let mut test_config = match self.objective { - TestObjective::TestSequence(options) => { - let test_sequence = - core::configuration::TestSequence::new_from_file(Path::new(&options.path)) - .expect("File exists") - .into_vec(); - let num_steps = test_sequence.len(); - gum::info!( - "{}", - format!("Sequence contains {} step(s)", num_steps).bright_purple() - ); - for (index, test_config) in test_sequence.into_iter().enumerate() { - gum::info!(target: LOG_TARGET, "{}", format!("Step {}/{}", index + 1, num_steps).bright_purple(),); - display_configuration(&test_config); - - match test_config.objective { - TestObjective::DataAvailabilityRead(ref _opts) => { - let mut state = TestState::new(&test_config); - let (mut env, _protocol_config) = prepare_test(test_config, &mut state); - env.runtime().block_on(availability::benchmark_availability_read( - &mut env, state, - )); - }, - TestObjective::DataAvailabilityWrite => { - let mut state = TestState::new(&test_config); - let (mut env, _protocol_config) = prepare_test(test_config, &mut state); - env.runtime().block_on(availability::benchmark_availability_write( - &mut env, state, - )); - }, - _ => gum::error!("Invalid test objective in sequence"), - } - } - return Ok(()) - }, - TestObjective::DataAvailabilityRead(ref _options) => self.create_test_configuration(), - TestObjective::DataAvailabilityWrite => self.create_test_configuration(), - }; - - let mut latency_config = test_config.latency.clone().unwrap_or_default(); - - if let Some(latency) = self.peer_mean_latency { - latency_config.mean_latency_ms = latency; - } - - if let Some(std_dev) = self.peer_latency_std_dev { - latency_config.std_dev = std_dev; - } - - // Write back the updated latency. - test_config.latency = Some(latency_config); - - if let Some(connectivity) = self.connectivity { - test_config.connectivity = connectivity; - } - - if let Some(bandwidth) = self.peer_bandwidth { - // CLI expects bw in KiB - test_config.peer_bandwidth = bandwidth * 1024; - } - - if let Some(bandwidth) = self.bandwidth { - // CLI expects bw in KiB - test_config.bandwidth = bandwidth * 1024; - } - - display_configuration(&test_config); - - let mut state = TestState::new(&test_config); - let (mut env, _protocol_config) = prepare_test(test_config, &mut state); - - match self.objective { - TestObjective::DataAvailabilityRead(_options) => { - env.runtime() - .block_on(availability::benchmark_availability_read(&mut env, state)); - }, - TestObjective::DataAvailabilityWrite => { - env.runtime() - .block_on(availability::benchmark_availability_write(&mut env, state)); - }, - TestObjective::TestSequence(_options) => {}, + let test_sequence = core::configuration::TestSequence::new_from_file(Path::new(&self.path)) + .expect("File exists") + .into_vec(); + let num_steps = test_sequence.len(); + gum::info!("{}", format!("Sequence contains {} step(s)", num_steps).bright_purple()); + for (index, test_config) in test_sequence.into_iter().enumerate() { + let benchmark_name = format!("{} #{} {}", &self.path, index + 1, test_config.objective); + gum::info!(target: LOG_TARGET, "{}", format!("Step {}/{}", index + 1, num_steps).bright_purple(),); + display_configuration(&test_config); + + let usage = match test_config.objective { + TestObjective::DataAvailabilityRead(ref _opts) => { + let mut state = TestState::new(&test_config); + let (mut env, _protocol_config) = prepare_test(test_config, &mut state); + env.runtime().block_on(availability::benchmark_availability_read( + &benchmark_name, + &mut env, + state, + )) + }, + TestObjective::ApprovalVoting(ref options) => { + let (mut env, state) = + approval::prepare_test(test_config.clone(), options.clone()); + env.runtime().block_on(bench_approvals(&benchmark_name, &mut env, state)) + }, + TestObjective::DataAvailabilityWrite => { + let mut state = TestState::new(&test_config); + let (mut env, _protocol_config) = prepare_test(test_config, &mut state); + env.runtime().block_on(availability::benchmark_availability_write( + &benchmark_name, + &mut env, + state, + )) + }, + TestObjective::Unimplemented => todo!(), + }; + + let output = if self.yaml_output { + serde_yaml::to_string(&vec![usage])? + } else { + usage.to_string() + }; + println!("{}", output); } if let Some(agent_running) = agent_running { @@ -251,7 +210,6 @@ fn main() -> eyre::Result<()> { .filter(Some("polkadot_overseer"), log::LevelFilter::Error) .filter(None, log::LevelFilter::Info) .format_timestamp_millis() - // .filter(None, log::LevelFilter::Trace) .try_init() .unwrap(); diff --git a/polkadot/node/subsystem-types/Cargo.toml b/polkadot/node/subsystem-types/Cargo.toml index ffd05cca2e9306ca8cb311cc8c93e9a65c0732ff..54c8f7e2ade773f1df84fc8cef1825c266364633 100644 --- a/polkadot/node/subsystem-types/Cargo.toml +++ b/polkadot/node/subsystem-types/Cargo.toml @@ -28,6 +28,6 @@ sc-client-api = { path = "../../../substrate/client/api" } sc-transaction-pool-api = { path = "../../../substrate/client/transaction-pool/api" } smallvec = "1.8.0" substrate-prometheus-endpoint = { path = "../../../substrate/utils/prometheus" } -thiserror = "1.0.48" +thiserror = { workspace = true } async-trait = "0.1.74" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 1d5d82b57fdfb482383bc943d22ee61a512f035b..549e43a671d6b4f9bd59659647cdaff8a69125d4 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -1128,14 +1128,16 @@ pub enum ProspectiveParachainsMessage { /// has been backed. This requires that the candidate was successfully introduced in /// the past. CandidateBacked(ParaId, CandidateHash), - /// Get a backable candidate hash along with its relay parent for the given parachain, + /// Get N backable candidate hashes along with their relay parents for the given parachain, /// under the given relay-parent hash, which is a descendant of the given candidate hashes. + /// N should represent the number of scheduled cores of this ParaId. /// Returns `None` on the channel if no such candidate exists. - GetBackableCandidate( + GetBackableCandidates( Hash, ParaId, + u32, Vec, - oneshot::Sender>, + oneshot::Sender>, ), /// Get the hypothetical frontier membership of candidates with the given properties /// under the specified active leaves' fragment trees. diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index 5f615e05abd4f2cf7526935d3093e2c9492b9eba..a668f8de76a0bdf15b1e4498924279b5fc467d3e 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -18,7 +18,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ parking_lot = "0.12.1" pin-project = "1.0.9" rand = "0.8.5" -thiserror = "1.0.48" +thiserror = { workspace = true } fatality = "0.0.6" gum = { package = "tracing-gum", path = "../gum" } derive_more = "0.99.17" @@ -46,7 +46,7 @@ parity-db = { version = "0.4.12" } assert_matches = "1.4.0" env_logger = "0.9.0" futures = { version = "0.3.21", features = ["thread-pool"] } -log = "0.4.17" +log = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { path = "../subsystem-test-helpers" } lazy_static = "1.4.0" polkadot-primitives-test-helpers = { path = "../../primitives/test-helpers" } diff --git a/polkadot/node/test/service/Cargo.toml b/polkadot/node/test/service/Cargo.toml index f04108537995f1caa0eb72833af2ca558fe931ec..e7892abcd87bf09140b53f5f123798b2ccce7ac4 100644 --- a/polkadot/node/test/service/Cargo.toml +++ b/polkadot/node/test/service/Cargo.toml @@ -14,7 +14,7 @@ futures = "0.3.21" hex = "0.4.3" gum = { package = "tracing-gum", path = "../../gum" } rand = "0.8.5" -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } tempfile = "3.2.0" tokio = "1.24.2" diff --git a/polkadot/node/test/service/src/chain_spec.rs b/polkadot/node/test/service/src/chain_spec.rs index 0295090b9521551533d012addeda064595dbeac3..4cc387317c3f0f15990825ee377e07ae82925073 100644 --- a/polkadot/node/test/service/src/chain_spec.rs +++ b/polkadot/node/test/service/src/chain_spec.rs @@ -169,6 +169,7 @@ fn polkadot_testnet_genesis( paras_availability_period: 4, no_show_slots: 10, minimum_validation_upgrade_delay: 5, + max_downward_message_size: 1024, ..Default::default() }, } diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index e4eec32baf2abc3d766f6daf444528ea3ad2a528..ca904bae28db2dcc2f0a2e50e1cf92528a135003 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -44,8 +44,8 @@ use sc_network::{ }; use sc_service::{ config::{ - DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, WasmExecutionMethod, - WasmtimeInstantiationStrategy, + DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, RpcBatchRequestConfig, + WasmExecutionMethod, WasmtimeInstantiationStrategy, }, BasePath, BlocksPruning, Configuration, Role, RpcHandlers, TaskManager, }; @@ -186,6 +186,8 @@ pub fn node_config( rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/polkadot/node/zombienet-backchannel/Cargo.toml b/polkadot/node/zombienet-backchannel/Cargo.toml index 6af7a8d6e380a0180bb83f670fd15deee7dfc21b..fa99490a997434eedee977121f13d8dcc1a9b5a1 100644 --- a/polkadot/node/zombienet-backchannel/Cargo.toml +++ b/polkadot/node/zombienet-backchannel/Cargo.toml @@ -19,7 +19,7 @@ futures-util = "0.3.30" lazy_static = "1.4.0" parity-scale-codec = { version = "3.6.1", features = ["derive"] } reqwest = { version = "0.11", features = ["rustls-tls"], default-features = false } -thiserror = "1.0.48" +thiserror = { workspace = true } gum = { package = "tracing-gum", path = "../gum" } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.111" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } diff --git a/polkadot/parachain/Cargo.toml b/polkadot/parachain/Cargo.toml index e5ff1051d25a3a972f22e9521a864b035332d500..d8c3cea7ad8b16062f346c12842d5f652936797c 100644 --- a/polkadot/parachain/Cargo.toml +++ b/polkadot/parachain/Cargo.toml @@ -24,7 +24,7 @@ derive_more = "0.99.11" bounded-collections = { version = "0.2.0", default-features = false, features = ["serde"] } # all optional crates. -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } [features] default = ["std"] diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index 7dd0d9a563c5087b9295d5de5d6a6ee812033578..8ce4ceb47c245b621dcbf0199b7a73ad29b1a6f5 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -16,10 +16,10 @@ path = "src/main.rs" [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } test-parachain-adder = { path = ".." } polkadot-primitives = { path = "../../../../primitives" } diff --git a/polkadot/parachain/test-parachains/undying/Cargo.toml b/polkadot/parachain/test-parachains/undying/Cargo.toml index 19e1261db1e7c4f17308061929d559df34943159..82ceebcf4eee99f36140908580b41c13b04ea87e 100644 --- a/polkadot/parachain/test-parachains/undying/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/Cargo.toml @@ -17,7 +17,7 @@ parity-scale-codec = { version = "3.6.1", default-features = false, features = [ sp-std = { path = "../../../../substrate/primitives/std", default-features = false } tiny-keccak = { version = "2.0.2", features = ["keccak"] } dlmalloc = { version = "0.2.4", features = ["global"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } # We need to make sure the global allocator is disabled until we have support of full substrate externalities sp-io = { path = "../../../../substrate/primitives/io", default-features = false, features = ["disable_allocator"] } diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index 001c48476b58929852075fd914154b0b86daa93a..25fdbfa74ea08ae3a2b389927073aeadd066cb7d 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -16,10 +16,10 @@ path = "src/main.rs" [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } test-parachain-undying = { path = ".." } polkadot-primitives = { path = "../../../../primitives" } diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index c2fdf331568d6e69350542f5c4186b916474987a..e63fb621c7880c03fe0282a5500df69d20528f99 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -14,7 +14,8 @@ bitvec = { version = "1.0.0", default-features = false, features = ["alloc", "se hex-literal = "0.4.1" parity-scale-codec = { version = "3.6.1", default-features = false, features = ["bit-vec", "derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["bit-vec", "derive", "serde"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +log = { workspace = true, default-features = false } +serde = { features = ["alloc", "derive"], workspace = true } application-crypto = { package = "sp-application-crypto", path = "../../substrate/primitives/application-crypto", default-features = false, features = ["serde"] } inherents = { package = "sp-inherents", path = "../../substrate/primitives/inherents", default-features = false } @@ -38,6 +39,7 @@ std = [ "application-crypto/std", "bitvec/std", "inherents/std", + "log/std", "parity-scale-codec/std", "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs index 2570bcadf606ab8ef0809a1f258a6068263f1db1..2ddd9b58dfe45d6560e9dc5c62f3d047ad8d9850 100644 --- a/polkadot/primitives/src/lib.rs +++ b/polkadot/primitives/src/lib.rs @@ -57,8 +57,8 @@ pub use v6::{ UpgradeRestriction, UpwardMessage, ValidDisputeStatementKind, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation, ValidityError, ASSIGNMENT_KEY_TYPE_ID, LEGACY_MIN_BACKING_VOTES, LOWEST_PUBLIC_ID, - MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, - PARACHAINS_INHERENT_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, + MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, MIN_CODE_SIZE, + ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, PARACHAINS_INHERENT_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, }; #[cfg(feature = "std")] diff --git a/polkadot/primitives/src/v6/mod.rs b/polkadot/primitives/src/v6/mod.rs index fd0b32db799434d1b76071d53edf1e20a491c109..89431f7801f7ae94390763f0ef710b54cb301718 100644 --- a/polkadot/primitives/src/v6/mod.rs +++ b/polkadot/primitives/src/v6/mod.rs @@ -16,7 +16,7 @@ //! `V6` Primitives. -use bitvec::vec::BitVec; +use bitvec::{field::BitField, slice::BitSlice, vec::BitVec}; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_std::{ @@ -72,6 +72,7 @@ pub use metrics::{ /// The key type ID for a collator key. pub const COLLATOR_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"coll"); +const LOG_TARGET: &str = "runtime::primitives"; mod collator_app { use application_crypto::{app_crypto, sr25519}; @@ -362,6 +363,9 @@ pub const PARACHAINS_INHERENT_IDENTIFIER: InherentIdentifier = *b"parachn0"; /// The key type ID for parachain assignment key. pub const ASSIGNMENT_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"asgn"); +/// Compressed or not the wasm blob can never be less than 9 bytes. +pub const MIN_CODE_SIZE: u32 = 9; + /// Maximum compressed code size we support right now. /// At the moment we have runtime upgrade on chain, which restricts scalability severely. If we want /// to have bigger values, we should fix that first. @@ -703,19 +707,50 @@ pub type UncheckedSignedAvailabilityBitfields = Vec { /// The candidate referred to. - pub candidate: CommittedCandidateReceipt, + candidate: CommittedCandidateReceipt, /// The validity votes themselves, expressed as signatures. - pub validity_votes: Vec, - /// The indices of the validators within the group, expressed as a bitfield. - pub validator_indices: BitVec, + validity_votes: Vec, + /// The indices of the validators within the group, expressed as a bitfield. May be extended + /// beyond the backing group size to contain the assigned core index, if ElasticScalingMVP is + /// enabled. + validator_indices: BitVec, } impl BackedCandidate { - /// Get a reference to the descriptor of the para. + /// Constructor + pub fn new( + candidate: CommittedCandidateReceipt, + validity_votes: Vec, + validator_indices: BitVec, + core_index: Option, + ) -> Self { + let mut instance = Self { candidate, validity_votes, validator_indices }; + if let Some(core_index) = core_index { + instance.inject_core_index(core_index); + } + instance + } + + /// Get a reference to the descriptor of the candidate. pub fn descriptor(&self) -> &CandidateDescriptor { &self.candidate.descriptor } + /// Get a reference to the committed candidate receipt of the candidate. + pub fn candidate(&self) -> &CommittedCandidateReceipt { + &self.candidate + } + + /// Get a reference to the validity votes of the candidate. + pub fn validity_votes(&self) -> &[ValidityAttestation] { + &self.validity_votes + } + + /// Get a mutable reference to validity votes of the para. + pub fn validity_votes_mut(&mut self) -> &mut Vec { + &mut self.validity_votes + } + /// Compute this candidate's hash. pub fn hash(&self) -> CandidateHash where @@ -731,6 +766,48 @@ impl BackedCandidate { { self.candidate.to_plain() } + + /// Get a copy of the validator indices and the assumed core index, if any. + pub fn validator_indices_and_core_index( + &self, + core_index_enabled: bool, + ) -> (&BitSlice, Option) { + // This flag tells us if the block producers must enable Elastic Scaling MVP hack. + // It extends `BackedCandidate::validity_indices` to store a 8 bit core index. + if core_index_enabled { + let core_idx_offset = self.validator_indices.len().saturating_sub(8); + if core_idx_offset > 0 { + let (validator_indices_slice, core_idx_slice) = + self.validator_indices.split_at(core_idx_offset); + return ( + validator_indices_slice, + Some(CoreIndex(core_idx_slice.load::() as u32)), + ); + } + } + + (&self.validator_indices, None) + } + + /// Inject a core index in the validator_indices bitvec. + fn inject_core_index(&mut self, core_index: CoreIndex) { + let core_index_to_inject: BitVec = + BitVec::from_vec(vec![core_index.0 as u8]); + self.validator_indices.extend(core_index_to_inject); + } + + /// Update the validator indices and core index in the candidate. + pub fn set_validator_indices_and_core_index( + &mut self, + new_indices: BitVec, + maybe_core_index: Option, + ) { + self.validator_indices = new_indices; + + if let Some(core_index) = maybe_core_index { + self.inject_core_index(core_index); + } + } } /// Verify the backing of the given candidate. @@ -743,43 +820,65 @@ impl BackedCandidate { /// /// Returns either an error, indicating that one of the signatures was invalid or that the index /// was out-of-bounds, or the number of signatures checked. -pub fn check_candidate_backing + Clone + Encode>( - backed: &BackedCandidate, +pub fn check_candidate_backing + Clone + Encode + core::fmt::Debug>( + candidate_hash: CandidateHash, + validity_votes: &[ValidityAttestation], + validator_indices: &BitSlice, signing_context: &SigningContext, group_len: usize, validator_lookup: impl Fn(usize) -> Option, ) -> Result { - if backed.validator_indices.len() != group_len { + if validator_indices.len() != group_len { + log::debug!( + target: LOG_TARGET, + "Check candidate backing: indices mismatch: group_len = {} , indices_len = {}", + group_len, + validator_indices.len(), + ); return Err(()) } - if backed.validity_votes.len() > group_len { + if validity_votes.len() > group_len { + log::debug!( + target: LOG_TARGET, + "Check candidate backing: Too many votes, expected: {}, found: {}", + group_len, + validity_votes.len(), + ); return Err(()) } - // this is known, even in runtime, to be blake2-256. - let hash = backed.candidate.hash(); - let mut signed = 0; - for ((val_in_group_idx, _), attestation) in backed - .validator_indices + for ((val_in_group_idx, _), attestation) in validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) - .zip(backed.validity_votes.iter()) + .zip(validity_votes.iter()) { let validator_id = validator_lookup(val_in_group_idx).ok_or(())?; - let payload = attestation.signed_payload(hash, signing_context); + let payload = attestation.signed_payload(candidate_hash, signing_context); let sig = attestation.signature(); if sig.verify(&payload[..], &validator_id) { signed += 1; } else { + log::debug!( + target: LOG_TARGET, + "Check candidate backing: Invalid signature. validator_id = {:?}, validator_index = {} ", + validator_id, + val_in_group_idx, + ); return Err(()) } } - if signed != backed.validity_votes.len() { + if signed != validity_votes.len() { + log::error!( + target: LOG_TARGET, + "Check candidate backing: Too many signatures, expected = {}, found = {}", + validity_votes.len(), + signed, + ); return Err(()) } @@ -1856,6 +1955,34 @@ pub enum PvfExecKind { #[cfg(test)] mod tests { use super::*; + use bitvec::bitvec; + use primitives::sr25519; + + pub fn dummy_committed_candidate_receipt() -> CommittedCandidateReceipt { + let zeros = Hash::zero(); + + CommittedCandidateReceipt { + descriptor: CandidateDescriptor { + para_id: 0.into(), + relay_parent: zeros, + collator: CollatorId::from(sr25519::Public::from_raw([0; 32])), + persisted_validation_data_hash: zeros, + pov_hash: zeros, + erasure_root: zeros, + signature: CollatorSignature::from(sr25519::Signature([0u8; 64])), + para_head: zeros, + validation_code_hash: ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]).hash(), + }, + commitments: CandidateCommitments { + head_data: HeadData(vec![]), + upward_messages: vec![].try_into().expect("empty vec fits within bounds"), + new_validation_code: None, + horizontal_messages: vec![].try_into().expect("empty vec fits within bounds"), + processed_downward_messages: 0, + hrmp_watermark: 0_u32, + }, + } + } #[test] fn group_rotation_info_calculations() { @@ -1930,4 +2057,73 @@ mod tests { assert!(zero_b.leading_zeros() >= zero_u.leading_zeros()); } + + #[test] + fn test_backed_candidate_injected_core_index() { + let initial_validator_indices = bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1]; + let mut candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + initial_validator_indices.clone(), + None, + ); + + // No core index supplied, ElasticScalingMVP is off. + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(false); + assert_eq!(validator_indices, initial_validator_indices.as_bitslice()); + assert!(core_index.is_none()); + + // No core index supplied, ElasticScalingMVP is on. Still, decoding will be ok if backing + // group size is <= 8, to give a chance to parachains that don't have multiple cores + // assigned. + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(true); + assert_eq!(validator_indices, initial_validator_indices.as_bitslice()); + assert!(core_index.is_none()); + + let encoded_validator_indices = candidate.validator_indices.clone(); + candidate.set_validator_indices_and_core_index(validator_indices.into(), core_index); + assert_eq!(candidate.validator_indices, encoded_validator_indices); + + // No core index supplied, ElasticScalingMVP is on. Decoding is corrupted if backing group + // size larger than 8. + let candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1, 0, 1, 0, 1, 0], + None, + ); + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(true); + assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0].as_bitslice()); + assert!(core_index.is_some()); + + // Core index supplied, ElasticScalingMVP is off. Core index will be treated as normal + // validator indices. Runtime will check against this. + let candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1], + Some(CoreIndex(10)), + ); + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(false); + assert_eq!( + validator_indices, + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0] + ); + assert!(core_index.is_none()); + + // Core index supplied, ElasticScalingMVP is on. + let mut candidate = BackedCandidate::new( + dummy_committed_candidate_receipt(), + vec![], + bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1], + Some(CoreIndex(10)), + ); + let (validator_indices, core_index) = candidate.validator_indices_and_core_index(true); + assert_eq!(validator_indices, bitvec![u8, bitvec::order::Lsb0; 0, 1, 0, 1]); + assert_eq!(core_index, Some(CoreIndex(10))); + + let encoded_validator_indices = candidate.validator_indices.clone(); + candidate.set_validator_indices_and_core_index(validator_indices.into(), core_index); + assert_eq!(candidate.validator_indices, encoded_validator_indices); + } } diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 630bcf8679ad3046ce734042a1557058ea440110..39d9dfc02c5bfa01281559852a5beed850aea1a8 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -64,9 +64,13 @@ pub mod node_features { /// Tells if tranch0 assignments could be sent in a single certificate. /// Reserved for: `` EnableAssignmentsV2 = 0, + /// This feature enables the extension of `BackedCandidate::validator_indices` by 8 bits. + /// The value stored there represents the assumed core index where the candidates + /// are backed. This is needed for the elastic scaling MVP. + ElasticScalingMVP = 1, /// First unassigned feature bit. /// Every time a new feature flag is assigned it should take this value. /// and this should be incremented. - FirstUnassigned = 1, + FirstUnassigned = 2, } } diff --git a/polkadot/primitives/test-helpers/src/lib.rs b/polkadot/primitives/test-helpers/src/lib.rs index d532d6ff57f4a34c53d7099d011f1ca68bf6cf5f..c0118f5960a47eabe205c4d2cf6bcf6d295ce347 100644 --- a/polkadot/primitives/test-helpers/src/lib.rs +++ b/polkadot/primitives/test-helpers/src/lib.rs @@ -126,7 +126,7 @@ pub fn dummy_candidate_descriptor>(relay_parent: H) -> CandidateD /// Create meaningless validation code. pub fn dummy_validation_code() -> ValidationCode { - ValidationCode(vec![1, 2, 3]) + ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]) } /// Create meaningless head data. diff --git a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md index 286aeddb986d3db3c7077d8d6227ebcf979073cb..8f00ff084941cc260dea6a9e76c0ff30d3770caf 100644 --- a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md +++ b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md @@ -92,9 +92,10 @@ prospective validation data. This is unlikely to change. been backed. - Sent by the Backing Subsystem after it successfully imports a statement giving a candidate the necessary quorum of backing votes. -- `ProspectiveParachainsMessage::GetBackableCandidate` - - Get a backable candidate hash along with its relay parent for a given parachain, - under a given relay-parent (leaf) hash, which is a descendant of given candidate hashes. +- `ProspectiveParachainsMessage::GetBackableCandidates` + - Get the requested number of backable candidate hashes along with their relay parent for a given + parachain,under a given relay-parent (leaf) hash, which are descendants of given candidate + hashes. - Sent by the Provisioner when requesting backable candidates, when selecting candidates for a given relay-parent. - `ProspectiveParachainsMessage::GetHypotheticalFrontier` diff --git a/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md b/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md index 0b4fe6a458732de10c2dcd23466ba30f9a46924c..b017259da8c0863e47deb87e916076d20ed95996 100644 --- a/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md +++ b/polkadot/roadmap/implementers-guide/src/node/utility/provisioner.md @@ -187,16 +187,16 @@ this process is a vector of `CandidateHash`s, sorted in order of their core inde #### Required Path -Required path is a parameter for `ProspectiveParachainsMessage::GetBackableCandidate`, which the provisioner sends in +Required path is a parameter for `ProspectiveParachainsMessage::GetBackableCandidates`, which the provisioner sends in candidate selection. -An empty required path indicates that the requested candidate should be a direct child of the most recently included +An empty required path indicates that the requested candidate chain should start with the most recently included parablock for the given `para_id` as of the given relay parent. In contrast, a required path with one or more entries prompts [prospective parachains](../backing/prospective-parachains.md) to step forward through its fragment tree for the given `para_id` and -relay parent until the desired parablock is reached. We then select a direct child of that parablock to pass to the -provisioner. +relay parent until the desired parablock is reached. We then select the chain starting with the direct child of that +parablock to pass to the provisioner. The parablocks making up a required path do not need to have been previously seen as included in relay chain blocks. Thus the ability to provision backable candidates based on a required path effectively decouples backing from inclusion. diff --git a/polkadot/rpc/Cargo.toml b/polkadot/rpc/Cargo.toml index 98ce2e482ce1a381ec0df08a68ab124781294387..5af5e63b175380f7e695deb973ea48df52c326e4 100644 --- a/polkadot/rpc/Cargo.toml +++ b/polkadot/rpc/Cargo.toml @@ -10,7 +10,7 @@ description = "Polkadot specific RPC functionality." workspace = true [dependencies] -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } polkadot-primitives = { path = "../primitives" } sc-client-api = { path = "../../substrate/client/api" } sp-blockchain = { path = "../../substrate/primitives/blockchain" } diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index acbc845c41a3cbc6077bd0d4c743066369db6885..eae5d4fb2ef90a0acb515b863dfb0013c45bac89 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -13,11 +13,11 @@ workspace = true impl-trait-for-tuples = "0.2.2" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc"] } -serde_derive = { version = "1.0.117" } +serde = { features = ["alloc"], workspace = true } +serde_derive = { workspace = true } static_assertions = "1.1.0" sp-api = { path = "../../../substrate/primitives/api", default-features = false } @@ -58,8 +58,6 @@ runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parac slot-range-helper = { path = "slot_range_helper", default-features = false } xcm = { package = "staging-xcm", path = "../../xcm", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = "../../xcm/xcm-executor", default-features = false, optional = true } - -pallet-xcm-benchmarks = { path = "../../xcm/pallet-xcm-benchmarks", default-features = false, optional = true } xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", default-features = false } [dev-dependencies] @@ -69,7 +67,7 @@ pallet-babe = { path = "../../../substrate/frame/babe" } pallet-treasury = { path = "../../../substrate/frame/treasury" } sp-keystore = { path = "../../../substrate/primitives/keystore" } sp-keyring = { path = "../../../substrate/primitives/keyring" } -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } libsecp256k1 = "0.7.0" test-helpers = { package = "polkadot-primitives-test-helpers", path = "../../primitives/test-helpers" } @@ -99,7 +97,6 @@ std = [ "pallet-transaction-payment/std", "pallet-treasury/std", "pallet-vesting/std", - "pallet-xcm-benchmarks/std", "parity-scale-codec/std", "primitives/std", "runtime-parachains/std", @@ -137,7 +134,6 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", - "pallet-xcm-benchmarks/runtime-benchmarks", "primitives/runtime-benchmarks", "runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/polkadot/runtime/common/src/impls.rs b/polkadot/runtime/common/src/impls.rs index 1ddad37831b68582cfc14f882870922c67058f1d..4ba85a3c8353565ad17cfb5db29c3d4e1dfa8a44 100644 --- a/polkadot/runtime/common/src/impls.rs +++ b/polkadot/runtime/common/src/impls.rs @@ -107,7 +107,7 @@ pub fn era_payout( )] pub enum VersionedLocatableAsset { #[codec(index = 3)] - V3 { location: xcm::v3::MultiLocation, asset_id: xcm::v3::AssetId }, + V3 { location: xcm::v3::Location, asset_id: xcm::v3::AssetId }, #[codec(index = 4)] V4 { location: xcm::v4::Location, asset_id: xcm::v4::AssetId }, } @@ -140,7 +140,7 @@ impl TryConvert<&VersionedLocation, xcm::latest::Location> for VersionedLocation ) -> Result { let latest = match location.clone() { VersionedLocation::V2(l) => { - let v3: xcm::v3::MultiLocation = l.try_into().map_err(|_| location)?; + let v3: xcm::v3::Location = l.try_into().map_err(|_| location)?; v3.try_into().map_err(|_| location)? }, VersionedLocation::V3(l) => l.try_into().map_err(|_| location)?, @@ -191,11 +191,11 @@ pub mod benchmarks { { fn create_asset_kind(seed: u32) -> VersionedLocatableAsset { VersionedLocatableAsset::V3 { - location: xcm::v3::MultiLocation::new( + location: xcm::v3::Location::new( Parents::get(), [xcm::v3::Junction::Parachain(ParaId::get())], ), - asset_id: xcm::v3::MultiLocation::new( + asset_id: xcm::v3::Location::new( 0, [ xcm::v3::Junction::PalletInstance(seed.try_into().unwrap()), diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index f551743b395cbdb3ffa1748b75c2a48d782a0d4a..3aa291f0f1f3075670cea40cee25645456057bdb 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -36,6 +36,7 @@ use pallet_identity::{self, legacy::IdentityInfo}; use parity_scale_codec::Encode; use primitives::{ BlockNumber, HeadData, Id as ParaId, SessionIndex, ValidationCode, LOWEST_PUBLIC_ID, + MAX_CODE_SIZE, }; use runtime_parachains::{ configuration, origin, paras, shared, Origin as ParaOrigin, ParaLifecycle, @@ -315,7 +316,7 @@ pub fn new_test_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); configuration::GenesisConfig:: { config: configuration::HostConfiguration { - max_code_size: 2 * 1024 * 1024, // 2 MB + max_code_size: MAX_CODE_SIZE, max_head_data_size: 1 * 1024 * 1024, // 1 MB ..Default::default() }, diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index c05c8a1ae00ba4e8a5a97095ad15a0a9a618cbbe..5450c4309e40745f36ae405a8614fe9e8ad2f534 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -26,7 +26,7 @@ use frame_support::{ traits::{Currency, Get, ReservableCurrency}, }; use frame_system::{self, ensure_root, ensure_signed}; -use primitives::{HeadData, Id as ParaId, ValidationCode, LOWEST_PUBLIC_ID}; +use primitives::{HeadData, Id as ParaId, ValidationCode, LOWEST_PUBLIC_ID, MIN_CODE_SIZE}; use runtime_parachains::{ configuration, ensure_parachain, paras::{self, ParaGenesisArgs, SetGoAhead}, @@ -182,8 +182,8 @@ pub mod pallet { ParaLocked, /// The ID given for registration has not been reserved. NotReserved, - /// Registering parachain with empty code is not allowed. - EmptyCode, + /// The validation code is invalid. + InvalidCode, /// Cannot perform a parachain slot / lifecycle swap. Check that the state of both paras /// are correct for the swap to work. CannotSwap, @@ -657,7 +657,7 @@ impl Pallet { para_kind: ParaKind, ) -> Result<(ParaGenesisArgs, BalanceOf), sp_runtime::DispatchError> { let config = configuration::Pallet::::config(); - ensure!(validation_code.0.len() > 0, Error::::EmptyCode); + ensure!(validation_code.0.len() >= MIN_CODE_SIZE as usize, Error::::InvalidCode); ensure!(validation_code.0.len() <= config.max_code_size as usize, Error::::CodeTooLarge); ensure!( genesis_head.0.len() <= config.max_head_data_size as usize, @@ -712,7 +712,7 @@ mod tests { }; use frame_system::limits; use pallet_balances::Error as BalancesError; - use primitives::{Balance, BlockNumber, SessionIndex}; + use primitives::{Balance, BlockNumber, SessionIndex, MAX_CODE_SIZE}; use runtime_parachains::{configuration, origin, shared}; use sp_core::H256; use sp_io::TestExternalities; @@ -849,7 +849,7 @@ mod tests { configuration::GenesisConfig:: { config: configuration::HostConfiguration { - max_code_size: 2 * 1024 * 1024, // 2 MB + max_code_size: MAX_CODE_SIZE, max_head_data_size: 1 * 1024 * 1024, // 1 MB ..Default::default() }, @@ -1032,6 +1032,51 @@ mod tests { }); } + #[test] + fn schedule_code_upgrade_validates_code() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); + assert_eq!(Balances::reserved_balance(&1), ::ParaDeposit::get()); + assert_ok!(Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + + let new_code = test_validation_code(0); + assert_noop!( + Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::::InvalidCode + ); + + let new_code = test_validation_code(max_code_size() as usize + 1); + assert_noop!( + Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::::InvalidCode + ); + }); + } + #[test] fn register_handles_basic_errors() { new_test_ext().execute_with(|| { @@ -1310,7 +1355,7 @@ mod tests { RuntimeOrigin::signed(1), para_id, vec![1; 3].into(), - vec![1, 2, 3].into(), + test_validation_code(32) )); assert_noop!(Registrar::add_lock(RuntimeOrigin::signed(2), para_id), BadOrigin); @@ -1473,7 +1518,7 @@ mod benchmarking { use crate::traits::Registrar as RegistrarT; use frame_support::assert_ok; use frame_system::RawOrigin; - use primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE}; + use primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MIN_CODE_SIZE}; use runtime_parachains::{paras, shared, Origin as ParaOrigin}; use sp_runtime::traits::Bounded; @@ -1604,7 +1649,7 @@ mod benchmarking { } schedule_code_upgrade { - let b in 1 .. MAX_CODE_SIZE; + let b in MIN_CODE_SIZE .. MAX_CODE_SIZE; let new_code = ValidationCode(vec![0; b as usize]); let para_id = ParaId::from(1000); }: _(RawOrigin::Root, para_id, new_code) diff --git a/polkadot/runtime/common/src/xcm_sender.rs b/polkadot/runtime/common/src/xcm_sender.rs index 7f1100a13619a8c5e1048afd4a34d2d977aa8914..46d09afcfb2c80215ed7ab18eb7be5750de1fcf6 100644 --- a/polkadot/runtime/common/src/xcm_sender.rs +++ b/polkadot/runtime/common/src/xcm_sender.rs @@ -136,7 +136,7 @@ where } } -/// Implementation of `pallet_xcm_benchmarks::EnsureDelivery` which helps to ensure delivery to the +/// Implementation of `xcm_builder::EnsureDelivery` which helps to ensure delivery to the /// `ParaId` parachain (sibling or child). Deposits existential deposit for origin (if needed). /// Deposits estimated fee to the origin account (if needed). /// Allows to trigger additional logic for specific `ParaId` (e.g. open HRMP channel) (if neeeded). @@ -164,7 +164,7 @@ impl< PriceForDelivery: PriceForMessageDelivery, Parachain: Get, ToParachainHelper: EnsureForParachain, - > pallet_xcm_benchmarks::EnsureDelivery + > xcm_builder::EnsureDelivery for ToParachainDeliveryHelper< XcmConfig, ExistentialDeposit, @@ -175,7 +175,7 @@ impl< { fn ensure_successful_delivery( origin_ref: &Location, - _dest: &Location, + dest: &Location, fee_reason: xcm_executor::traits::FeeReason, ) -> (Option, Option) { use xcm_executor::{ @@ -183,6 +183,15 @@ impl< FeesMode, }; + // check if the destination matches the expected `Parachain`. + if let Some(Parachain(para_id)) = dest.first_interior() { + if ParaId::from(*para_id) != Parachain::get().into() { + return (None, None) + } + } else { + return (None, None) + } + let mut fees_mode = None; if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) { // if not waived, we need to set up accounts for paying and receiving fees diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 507df01ac4816c07b5dd8533fff483c5489631a9..6104014547638dbf13c5b080a280e7cf369293c9 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -13,10 +13,10 @@ workspace = true impl-trait-for-tuples = "0.2.2" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } derive_more = "0.99.17" bitflags = "1.3.2" @@ -69,7 +69,8 @@ sp-tracing = { path = "../../../substrate/primitives/tracing" } sp-crypto-hashing = { path = "../../../substrate/primitives/crypto/hashing" } thousands = "0.2.0" assert_matches = "1" -serde_json = "1.0.111" +rstest = "0.18.2" +serde_json = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/polkadot/runtime/parachains/src/builder.rs b/polkadot/runtime/parachains/src/builder.rs index 016b3fca589a5b845110d9f25199cc8b8aef5bfe..500bc70cfa75294e5d08a590a6a90f3eb8b3acf1 100644 --- a/polkadot/runtime/parachains/src/builder.rs +++ b/polkadot/runtime/parachains/src/builder.rs @@ -587,11 +587,12 @@ impl BenchBuilder { }) .collect(); - BackedCandidate:: { + BackedCandidate::::new( candidate, validity_votes, - validator_indices: bitvec::bitvec![u8, bitvec::order::Lsb0; 1; group_validators.len()], - } + bitvec::bitvec![u8, bitvec::order::Lsb0; 1; group_validators.len()], + None, + ) }) .collect() } diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index 4619313590ebc2d5d92dfc85a4e845c381c61607..7cc5b31fc8fd1dfbed7a02b293eca2dab4b95cf4 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -281,7 +281,7 @@ impl> Default for HostConfiguration::set(Some(v10)); + v10::ActiveConfig::::set(Some(v10.clone())); v10::PendingConfigs::::set(Some(pending_configs)); migrate_to_v11::(); @@ -264,7 +264,7 @@ mod tests { let mut configs_to_check = v11::PendingConfigs::::get().unwrap(); configs_to_check.push((0, v11.clone())); - for (_, v10) in configs_to_check { + for (_, v11) in configs_to_check { #[rustfmt::skip] { assert_eq!(v10.max_code_size , v11.max_code_size); @@ -286,7 +286,7 @@ mod tests { assert_eq!(v10.hrmp_max_parachain_inbound_channels , v11.hrmp_max_parachain_inbound_channels); assert_eq!(v10.hrmp_channel_max_message_size , v11.hrmp_channel_max_message_size); assert_eq!(v10.code_retention_period , v11.code_retention_period); - assert_eq!(v10.coretime_cores , v11.coretime_cores); + assert_eq!(v10.on_demand_cores , v11.coretime_cores); assert_eq!(v10.on_demand_retries , v11.on_demand_retries); assert_eq!(v10.group_rotation_frequency , v11.group_rotation_frequency); assert_eq!(v10.paras_availability_period , v11.paras_availability_period); @@ -303,8 +303,8 @@ mod tests { assert_eq!(v10.minimum_validation_upgrade_delay , v11.minimum_validation_upgrade_delay); assert_eq!(v10.async_backing_params.allowed_ancestry_len, v11.async_backing_params.allowed_ancestry_len); assert_eq!(v10.async_backing_params.max_candidate_depth , v11.async_backing_params.max_candidate_depth); - assert_eq!(v10.executor_params , v11.executor_params); - assert_eq!(v10.minimum_backing_votes , v11.minimum_backing_votes); + assert_eq!(v10.executor_params , v11.executor_params); + assert_eq!(v10.minimum_backing_votes , v11.minimum_backing_votes); }; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression. } }); diff --git a/polkadot/runtime/parachains/src/configuration/tests.rs b/polkadot/runtime/parachains/src/configuration/tests.rs index c915eb12a0ca1712a1d923d126daac959a40cd09..f1570017dd7ba75db429eac4f00f26fa277de2d6 100644 --- a/polkadot/runtime/parachains/src/configuration/tests.rs +++ b/polkadot/runtime/parachains/src/configuration/tests.rs @@ -38,7 +38,7 @@ fn default_is_consistent() { fn scheduled_session_is_two_sessions_from_now() { new_test_ext(Default::default()).execute_with(|| { // The logic here is really tested only with scheduled_session = 2. It should work - // with other values, but that should receive a more rigorious testing. + // with other values, but that should receive a more rigorous testing. on_new_session(1); assert_eq!(Configuration::scheduled_session(), 3); }); @@ -136,7 +136,7 @@ fn pending_next_session_but_we_upgrade_once_more() { // update. assert_ok!(Configuration::set_validation_upgrade_cooldown(RuntimeOrigin::root(), 99)); - // This should result in yet another configiguration change scheduled. + // This should result in yet another configuration change scheduled. assert_eq!(Configuration::config(), initial_config); assert_eq!( PendingConfigs::::get(), @@ -179,7 +179,7 @@ fn scheduled_session_config_update_while_next_session_pending() { assert_ok!(Configuration::set_validation_upgrade_cooldown(RuntimeOrigin::root(), 99)); assert_ok!(Configuration::set_code_retention_period(RuntimeOrigin::root(), 98)); - // This should result in yet another configiguration change scheduled. + // This should result in yet another configuration change scheduled. assert_eq!(Configuration::config(), initial_config); assert_eq!( PendingConfigs::::get(), diff --git a/polkadot/runtime/parachains/src/coretime/migration.rs b/polkadot/runtime/parachains/src/coretime/migration.rs index e64d3fbd6a9ee53df561cf61199a66b537b0db64..9bc0a20ef5b4afce7d63da9bb1d231d39bf445ab 100644 --- a/polkadot/runtime/parachains/src/coretime/migration.rs +++ b/polkadot/runtime/parachains/src/coretime/migration.rs @@ -74,7 +74,7 @@ mod v_coretime { loop { match sp_io::storage::next_key(&next_key) { - // StorageVersion is initialized before, so we need to ingore it. + // StorageVersion is initialized before, so we need to ignore it. Some(key) if &key == &storage_version_key => { next_key = key; }, diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs index 90af9cde00a8faaec13101b490ea3ba92f4a827c..16e2e93b5617f2c85bbb308ff5d057c7ed99db9c 100644 --- a/polkadot/runtime/parachains/src/inclusion/mod.rs +++ b/polkadot/runtime/parachains/src/inclusion/mod.rs @@ -47,10 +47,7 @@ use scale_info::TypeInfo; use sp_runtime::{traits::One, DispatchError, SaturatedConversion, Saturating}; #[cfg(feature = "std")] use sp_std::fmt; -use sp_std::{ - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, - prelude::*, -}; +use sp_std::{collections::btree_set::BTreeSet, prelude::*}; pub use pallet::*; @@ -601,18 +598,16 @@ impl Pallet { /// scheduled cores. If these conditions are not met, the execution of the function fails. pub(crate) fn process_candidates( allowed_relay_parents: &AllowedRelayParentsTracker>, - candidates: Vec>, - scheduled: &BTreeMap, + candidates: Vec<(BackedCandidate, CoreIndex)>, group_validators: GV, + core_index_enabled: bool, ) -> Result, DispatchError> where GV: Fn(GroupIndex) -> Option>, { let now = >::block_number(); - ensure!(candidates.len() <= scheduled.len(), Error::::UnscheduledCandidate); - - if scheduled.is_empty() { + if candidates.is_empty() { return Ok(ProcessedCandidates::default()) } @@ -648,7 +643,7 @@ impl Pallet { // // In the meantime, we do certain sanity checks on the candidates and on the scheduled // list. - for (candidate_idx, backed_candidate) in candidates.iter().enumerate() { + for (candidate_idx, (backed_candidate, core_index)) in candidates.iter().enumerate() { let relay_parent_hash = backed_candidate.descriptor().relay_parent; let para_id = backed_candidate.descriptor().para_id; @@ -663,7 +658,7 @@ impl Pallet { let relay_parent_number = match check_ctx.verify_backed_candidate( &allowed_relay_parents, candidate_idx, - backed_candidate, + backed_candidate.candidate(), )? { Err(FailedToCreatePVD) => { log::debug!( @@ -679,11 +674,22 @@ impl Pallet { Ok(rpn) => rpn, }; - let para_id = backed_candidate.descriptor().para_id; + let (validator_indices, _) = + backed_candidate.validator_indices_and_core_index(core_index_enabled); + + log::debug!( + target: LOG_TARGET, + "Candidate {:?} on {:?}, + core_index_enabled = {}", + backed_candidate.hash(), + core_index, + core_index_enabled + ); + + check_assignment_in_order(core_index)?; + let mut backers = bitvec::bitvec![u8, BitOrderLsb0; 0; validators.len()]; - let core_idx = *scheduled.get(¶_id).ok_or(Error::::UnscheduledCandidate)?; - check_assignment_in_order(core_idx)?; ensure!( >::get(¶_id).is_none() && >::get(¶_id).is_none(), @@ -694,7 +700,7 @@ impl Pallet { // assigned to core at block `N + 1`. Thus, `relay_parent_number + 1` // will always land in the current session. let group_idx = >::group_assigned_to_core( - core_idx, + *core_index, relay_parent_number + One::one(), ) .ok_or_else(|| { @@ -711,7 +717,9 @@ impl Pallet { // check the signatures in the backing and that it is a majority. { let maybe_amount_validated = primitives::check_candidate_backing( - &backed_candidate, + backed_candidate.candidate().hash(), + backed_candidate.validity_votes(), + validator_indices, &signing_context, group_vals.len(), |intra_group_vi| { @@ -738,16 +746,15 @@ impl Pallet { let mut backer_idx_and_attestation = Vec::<(ValidatorIndex, ValidityAttestation)>::with_capacity( - backed_candidate.validator_indices.count_ones(), + validator_indices.count_ones(), ); let candidate_receipt = backed_candidate.receipt(); - for ((bit_idx, _), attestation) in backed_candidate - .validator_indices + for ((bit_idx, _), attestation) in validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) - .zip(backed_candidate.validity_votes.iter().cloned()) + .zip(backed_candidate.validity_votes().iter().cloned()) { let val_idx = group_vals.get(bit_idx).expect("this query succeeded above; qed"); @@ -760,7 +767,7 @@ impl Pallet { } core_indices_and_backers.push(( - (core_idx, para_id), + (*core_index, para_id), backers, group_idx, relay_parent_number, @@ -772,7 +779,7 @@ impl Pallet { // one more sweep for actually writing to storage. let core_indices = core_indices_and_backers.iter().map(|(c, ..)| *c).collect(); - for (candidate, (core, backers, group, relay_parent_number)) in + for ((candidate, _), (core, backers, group, relay_parent_number)) in candidates.into_iter().zip(core_indices_and_backers) { let para_id = candidate.descriptor().para_id; @@ -782,16 +789,18 @@ impl Pallet { bitvec::bitvec![u8, BitOrderLsb0; 0; validators.len()]; Self::deposit_event(Event::::CandidateBacked( - candidate.candidate.to_plain(), - candidate.candidate.commitments.head_data.clone(), + candidate.candidate().to_plain(), + candidate.candidate().commitments.head_data.clone(), core.0, group, )); - let candidate_hash = candidate.candidate.hash(); + let candidate_hash = candidate.candidate().hash(); - let (descriptor, commitments) = - (candidate.candidate.descriptor, candidate.candidate.commitments); + let (descriptor, commitments) = ( + candidate.candidate().descriptor.clone(), + candidate.candidate().commitments.clone(), + ); >::insert( ¶_id, @@ -1195,10 +1204,10 @@ impl CandidateCheckContext { &self, allowed_relay_parents: &AllowedRelayParentsTracker>, candidate_idx: usize, - backed_candidate: &BackedCandidate<::Hash>, + backed_candidate_receipt: &CommittedCandidateReceipt<::Hash>, ) -> Result, FailedToCreatePVD>, Error> { - let para_id = backed_candidate.descriptor().para_id; - let relay_parent = backed_candidate.descriptor().relay_parent; + let para_id = backed_candidate_receipt.descriptor().para_id; + let relay_parent = backed_candidate_receipt.descriptor().relay_parent; // Check that the relay-parent is one of the allowed relay-parents. let (relay_parent_storage_root, relay_parent_number) = { @@ -1223,13 +1232,13 @@ impl CandidateCheckContext { let expected = persisted_validation_data.hash(); ensure!( - expected == backed_candidate.descriptor().persisted_validation_data_hash, + expected == backed_candidate_receipt.descriptor().persisted_validation_data_hash, Error::::ValidationDataHashMismatch, ); } ensure!( - backed_candidate.descriptor().check_collator_signature().is_ok(), + backed_candidate_receipt.descriptor().check_collator_signature().is_ok(), Error::::NotCollatorSigned, ); @@ -1237,25 +1246,25 @@ impl CandidateCheckContext { // A candidate for a parachain without current validation code is not scheduled. .ok_or_else(|| Error::::UnscheduledCandidate)?; ensure!( - backed_candidate.descriptor().validation_code_hash == validation_code_hash, + backed_candidate_receipt.descriptor().validation_code_hash == validation_code_hash, Error::::InvalidValidationCodeHash, ); ensure!( - backed_candidate.descriptor().para_head == - backed_candidate.candidate.commitments.head_data.hash(), + backed_candidate_receipt.descriptor().para_head == + backed_candidate_receipt.commitments.head_data.hash(), Error::::ParaHeadMismatch, ); if let Err(err) = self.check_validation_outputs( para_id, relay_parent_number, - &backed_candidate.candidate.commitments.head_data, - &backed_candidate.candidate.commitments.new_validation_code, - backed_candidate.candidate.commitments.processed_downward_messages, - &backed_candidate.candidate.commitments.upward_messages, - BlockNumberFor::::from(backed_candidate.candidate.commitments.hrmp_watermark), - &backed_candidate.candidate.commitments.horizontal_messages, + &backed_candidate_receipt.commitments.head_data, + &backed_candidate_receipt.commitments.new_validation_code, + backed_candidate_receipt.commitments.processed_downward_messages, + &backed_candidate_receipt.commitments.upward_messages, + BlockNumberFor::::from(backed_candidate_receipt.commitments.hrmp_watermark), + &backed_candidate_receipt.commitments.horizontal_messages, ) { log::debug!( target: LOG_TARGET, diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs index 232e65d78ed2aef86aa7903d88cbe99236ba9ec1..d2b5a67c3e4538640b559bbf218539c122bf9d67 100644 --- a/polkadot/runtime/parachains/src/inclusion/tests.rs +++ b/polkadot/runtime/parachains/src/inclusion/tests.rs @@ -120,6 +120,7 @@ pub(crate) fn back_candidate( keystore: &KeystorePtr, signing_context: &SigningContext, kind: BackingKind, + core_index: Option, ) -> BackedCandidate { let mut validator_indices = bitvec::bitvec![u8, BitOrderLsb0; 0; group.len()]; let threshold = effective_minimum_backing_votes( @@ -155,15 +156,20 @@ pub(crate) fn back_candidate( validity_votes.push(ValidityAttestation::Explicit(signature).into()); } - let backed = BackedCandidate { candidate, validity_votes, validator_indices }; + let backed = + BackedCandidate::new(candidate, validity_votes, validator_indices.clone(), core_index); - let successfully_backed = - primitives::check_candidate_backing(&backed, signing_context, group.len(), |i| { - Some(validators[group[i].0 as usize].public().into()) - }) - .ok() - .unwrap_or(0) >= - threshold; + let successfully_backed = primitives::check_candidate_backing( + backed.candidate().hash(), + backed.validity_votes(), + validator_indices.as_bitslice(), + signing_context, + group.len(), + |i| Some(validators[group[i].0 as usize].public().into()), + ) + .ok() + .unwrap_or(0) >= + threshold; match kind { BackingKind::Unanimous | BackingKind::Threshold => assert!(successfully_backed), @@ -919,38 +925,16 @@ fn candidate_checks() { let thread_a_assignment = (thread_a, CoreIndex::from(2)); let allowed_relay_parents = default_allowed_relay_parent_tracker(); - // unscheduled candidate. - { - let mut candidate = TestCandidateBuilder { - para_id: chain_a, - relay_parent: System::parent_hash(), - pov_hash: Hash::repeat_byte(1), - persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), - hrmp_watermark: RELAY_PARENT_NUM, - ..Default::default() - } - .build(); - collator_sign_candidate(Sr25519Keyring::One, &mut candidate); - - let backed = back_candidate( - candidate, - &validators, - group_validators(GroupIndex::from(0)).unwrap().as_ref(), - &keystore, - &signing_context, - BackingKind::Threshold, - ); - - assert_noop!( - ParaInclusion::process_candidates( - &allowed_relay_parents, - vec![backed], - &[chain_b_assignment].into_iter().collect(), - &group_validators, - ), - Error::::UnscheduledCandidate - ); - } + // no candidates. + assert_eq!( + ParaInclusion::process_candidates( + &allowed_relay_parents, + vec![], + &group_validators, + false + ), + Ok(ProcessedCandidates::default()) + ); // candidates out of order. { @@ -984,6 +968,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -993,15 +978,16 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); // out-of-order manifests as unscheduled. assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_b, backed_a], - &[chain_a_assignment, chain_b_assignment].into_iter().collect(), + vec![(backed_b, chain_b_assignment.1), (backed_a, chain_a_assignment.1)], &group_validators, + false ), Error::::ScheduledOutOfOrder ); @@ -1027,14 +1013,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Lacking, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::InsufficientBacking ); @@ -1075,6 +1062,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -1084,14 +1072,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_b, backed_a], - &[chain_a_assignment, chain_b_assignment].into_iter().collect(), + vec![(backed_b, chain_b_assignment.1), (backed_a, chain_a_assignment.1)], &group_validators, + false ), Error::::DisallowedRelayParent ); @@ -1122,14 +1111,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[thread_a_assignment].into_iter().collect(), + vec![(backed, thread_a_assignment.1)], &group_validators, + false ), Error::::NotCollatorSigned ); @@ -1156,6 +1146,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let candidate = TestCandidateBuilder::default().build(); @@ -1177,9 +1168,9 @@ fn candidate_checks() { assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::CandidateScheduledBeforeParaFree ); @@ -1212,14 +1203,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::CandidateScheduledBeforeParaFree ); @@ -1233,7 +1225,7 @@ fn candidate_checks() { para_id: chain_a, relay_parent: System::parent_hash(), pov_hash: Hash::repeat_byte(1), - new_validation_code: Some(vec![5, 6, 7, 8].into()), + new_validation_code: Some(dummy_validation_code()), persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), hrmp_watermark: RELAY_PARENT_NUM, ..Default::default() @@ -1249,6 +1241,7 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); { @@ -1257,7 +1250,7 @@ fn candidate_checks() { assert_eq!(expected_at, 12); Paras::schedule_code_upgrade( chain_a, - vec![1, 2, 3, 4].into(), + vec![9, 8, 7, 6, 5, 4, 3, 2, 1].into(), expected_at, &cfg, SetGoAhead::Yes, @@ -1267,9 +1260,9 @@ fn candidate_checks() { assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::PrematureCodeUpgrade ); @@ -1296,14 +1289,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_eq!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Err(Error::::ValidationDataHashMismatch.into()), ); @@ -1317,7 +1311,7 @@ fn candidate_checks() { pov_hash: Hash::repeat_byte(1), persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), hrmp_watermark: RELAY_PARENT_NUM, - validation_code: ValidationCode(vec![1]), + validation_code: ValidationCode(vec![9, 8, 7, 6, 5, 4, 3, 2, 1]), ..Default::default() } .build(); @@ -1331,14 +1325,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::InvalidValidationCodeHash ); @@ -1366,14 +1361,15 @@ fn candidate_checks() { &keystore, &signing_context, BackingKind::Threshold, + None, ); assert_noop!( ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed], - &[chain_a_assignment].into_iter().collect(), + vec![(backed, chain_a_assignment.1)], &group_validators, + false ), Error::::ParaHeadMismatch ); @@ -1486,6 +1482,7 @@ fn backing_works() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -1495,6 +1492,7 @@ fn backing_works() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let backed_c = back_candidate( @@ -1504,15 +1502,20 @@ fn backing_works() { &keystore, &signing_context, BackingKind::Threshold, + None, ); - let backed_candidates = vec![backed_a.clone(), backed_b.clone(), backed_c]; + let backed_candidates = vec![ + (backed_a.clone(), chain_a_assignment.1), + (backed_b.clone(), chain_b_assignment.1), + (backed_c, thread_a_assignment.1), + ]; let get_backing_group_idx = { // the order defines the group implicitly for this test case let backed_candidates_with_groups = backed_candidates .iter() .enumerate() - .map(|(idx, backed_candidate)| (backed_candidate.hash(), GroupIndex(idx as _))) + .map(|(idx, (backed_candidate, _))| (backed_candidate.hash(), GroupIndex(idx as _))) .collect::>(); move |candidate_hash_x: CandidateHash| -> Option { @@ -1532,10 +1535,8 @@ fn backing_works() { } = ParaInclusion::process_candidates( &allowed_relay_parents, backed_candidates.clone(), - &[chain_a_assignment, chain_b_assignment, thread_a_assignment] - .into_iter() - .collect(), &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); @@ -1554,22 +1555,22 @@ fn backing_works() { CandidateHash, (CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>), >::new(); - backed_candidates.into_iter().for_each(|backed_candidate| { + backed_candidates.into_iter().for_each(|(backed_candidate, _)| { let candidate_receipt_with_backers = intermediate .entry(backed_candidate.hash()) .or_insert_with(|| (backed_candidate.receipt(), Vec::new())); - - assert_eq!( - backed_candidate.validity_votes.len(), - backed_candidate.validator_indices.count_ones() - ); + let (validator_indices, None) = + backed_candidate.validator_indices_and_core_index(false) + else { + panic!("Expected no injected core index") + }; + assert_eq!(backed_candidate.validity_votes().len(), validator_indices.count_ones()); candidate_receipt_with_backers.1.extend( - backed_candidate - .validator_indices + validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) - .zip(backed_candidate.validity_votes.iter().cloned()) + .zip(backed_candidate.validity_votes().iter().cloned()) .filter_map(|((validator_index_within_group, _), attestation)| { let grp_idx = get_backing_group_idx(backed_candidate.hash()).unwrap(); group_validators(grp_idx).map(|validator_indices| { @@ -1666,6 +1667,257 @@ fn backing_works() { }); } +#[test] +fn backing_works_with_elastic_scaling_mvp() { + let chain_a = ParaId::from(1_u32); + let chain_b = ParaId::from(2_u32); + let thread_a = ParaId::from(3_u32); + + // The block number of the relay-parent for testing. + const RELAY_PARENT_NUM: BlockNumber = 4; + + let paras = vec![ + (chain_a, ParaKind::Parachain), + (chain_b, ParaKind::Parachain), + (thread_a, ParaKind::Parathread), + ]; + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + ]; + let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); + for validator in validators.iter() { + Keystore::sr25519_generate_new( + &*keystore, + PARACHAIN_KEY_TYPE_ID, + Some(&validator.to_seed()), + ) + .unwrap(); + } + let validator_public = validator_pubkeys(&validators); + + new_test_ext(genesis_config(paras)).execute_with(|| { + shared::Pallet::::set_active_validators_ascending(validator_public.clone()); + shared::Pallet::::set_session_index(5); + + run_to_block(5, |_| None); + + let signing_context = + SigningContext { parent_hash: System::parent_hash(), session_index: 5 }; + + let group_validators = |group_index: GroupIndex| { + match group_index { + group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), + group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), + group_index if group_index == GroupIndex::from(2) => Some(vec![4]), + _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), + } + .map(|vs| vs.into_iter().map(ValidatorIndex).collect::>()) + }; + + // When processing candidates, we compute the group index from scheduler. + let validator_groups = vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2), ValidatorIndex(3)], + vec![ValidatorIndex(4)], + ]; + Scheduler::set_validator_groups(validator_groups); + + let allowed_relay_parents = default_allowed_relay_parent_tracker(); + + let mut candidate_a = TestCandidateBuilder { + para_id: chain_a, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(1), + persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + collator_sign_candidate(Sr25519Keyring::One, &mut candidate_a); + + let mut candidate_b_1 = TestCandidateBuilder { + para_id: chain_b, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(2), + persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + collator_sign_candidate(Sr25519Keyring::One, &mut candidate_b_1); + + let mut candidate_b_2 = TestCandidateBuilder { + para_id: chain_b, + relay_parent: System::parent_hash(), + pov_hash: Hash::repeat_byte(3), + persisted_validation_data_hash: make_vdata_hash(chain_b).unwrap(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + collator_sign_candidate(Sr25519Keyring::One, &mut candidate_b_2); + + let backed_a = back_candidate( + candidate_a.clone(), + &validators, + group_validators(GroupIndex::from(0)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + None, + ); + + let backed_b_1 = back_candidate( + candidate_b_1.clone(), + &validators, + group_validators(GroupIndex::from(1)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + Some(CoreIndex(1)), + ); + + let backed_b_2 = back_candidate( + candidate_b_2.clone(), + &validators, + group_validators(GroupIndex::from(2)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + Some(CoreIndex(2)), + ); + + let backed_candidates = vec![ + (backed_a.clone(), CoreIndex(0)), + (backed_b_1.clone(), CoreIndex(1)), + (backed_b_2.clone(), CoreIndex(2)), + ]; + let get_backing_group_idx = { + // the order defines the group implicitly for this test case + let backed_candidates_with_groups = backed_candidates + .iter() + .enumerate() + .map(|(idx, (backed_candidate, _))| (backed_candidate.hash(), GroupIndex(idx as _))) + .collect::>(); + + move |candidate_hash_x: CandidateHash| -> Option { + backed_candidates_with_groups.iter().find_map(|(candidate_hash, grp)| { + if *candidate_hash == candidate_hash_x { + Some(*grp) + } else { + None + } + }) + } + }; + + let ProcessedCandidates { + core_indices: occupied_cores, + candidate_receipt_with_backing_validator_indices, + } = ParaInclusion::process_candidates( + &allowed_relay_parents, + backed_candidates.clone(), + &group_validators, + true, + ) + .expect("candidates scheduled, in order, and backed"); + + // Both b candidates will be backed. However, only one will be recorded on-chain and proceed + // with being made available. + assert_eq!( + occupied_cores, + vec![ + (CoreIndex::from(0), chain_a), + (CoreIndex::from(1), chain_b), + (CoreIndex::from(2), chain_b), + ] + ); + + // Transform the votes into the setup we expect + let mut expected = std::collections::HashMap::< + CandidateHash, + (CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>), + >::new(); + backed_candidates.into_iter().for_each(|(backed_candidate, _)| { + let candidate_receipt_with_backers = expected + .entry(backed_candidate.hash()) + .or_insert_with(|| (backed_candidate.receipt(), Vec::new())); + let (validator_indices, _maybe_core_index) = + backed_candidate.validator_indices_and_core_index(true); + assert_eq!(backed_candidate.validity_votes().len(), validator_indices.count_ones()); + candidate_receipt_with_backers.1.extend( + validator_indices + .iter() + .enumerate() + .filter(|(_, signed)| **signed) + .zip(backed_candidate.validity_votes().iter().cloned()) + .filter_map(|((validator_index_within_group, _), attestation)| { + let grp_idx = get_backing_group_idx(backed_candidate.hash()).unwrap(); + group_validators(grp_idx).map(|validator_indices| { + (validator_indices[validator_index_within_group], attestation) + }) + }), + ); + }); + + assert_eq!( + expected, + candidate_receipt_with_backing_validator_indices + .into_iter() + .map(|c| (c.0.hash(), c)) + .collect() + ); + + let backers = { + let num_backers = effective_minimum_backing_votes( + group_validators(GroupIndex(0)).unwrap().len(), + configuration::Pallet::::config().minimum_backing_votes, + ); + backing_bitfield(&(0..num_backers).collect::>()) + }; + assert_eq!( + >::get(&chain_a), + Some(CandidatePendingAvailability { + core: CoreIndex::from(0), + hash: candidate_a.hash(), + descriptor: candidate_a.descriptor, + availability_votes: default_availability_votes(), + relay_parent_number: System::block_number() - 1, + backed_in_number: System::block_number(), + backers, + backing_group: GroupIndex::from(0), + }) + ); + assert_eq!( + >::get(&chain_a), + Some(candidate_a.commitments), + ); + + // Only one candidate for b will be recorded on chain. + assert_eq!( + >::get(&chain_b), + Some(CandidatePendingAvailability { + core: CoreIndex::from(2), + hash: candidate_b_2.hash(), + descriptor: candidate_b_2.descriptor, + availability_votes: default_availability_votes(), + relay_parent_number: System::block_number() - 1, + backed_in_number: System::block_number(), + backers: backing_bitfield(&[4]), + backing_group: GroupIndex::from(2), + }) + ); + assert_eq!( + >::get(&chain_b), + Some(candidate_b_2.commitments), + ); + }); +} + #[test] fn can_include_candidate_with_ok_code_upgrade() { let chain_a = ParaId::from(1_u32); @@ -1726,7 +1978,7 @@ fn can_include_candidate_with_ok_code_upgrade() { relay_parent: System::parent_hash(), pov_hash: Hash::repeat_byte(1), persisted_validation_data_hash: make_vdata_hash(chain_a).unwrap(), - new_validation_code: Some(vec![1, 2, 3].into()), + new_validation_code: Some(vec![9, 8, 7, 6, 5, 4, 3, 2, 1].into()), hrmp_watermark: RELAY_PARENT_NUM, ..Default::default() } @@ -1740,14 +1992,15 @@ fn can_include_candidate_with_ok_code_upgrade() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let ProcessedCandidates { core_indices: occupied_cores, .. } = ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_a], - &[chain_a_assignment].into_iter().collect(), + vec![(backed_a, chain_a_assignment.1)], &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); @@ -1932,6 +2185,7 @@ fn check_allowed_relay_parents() { &keystore, &signing_context_a, BackingKind::Threshold, + None, ); let backed_b = back_candidate( @@ -1941,6 +2195,7 @@ fn check_allowed_relay_parents() { &keystore, &signing_context_b, BackingKind::Threshold, + None, ); let backed_c = back_candidate( @@ -1950,17 +2205,20 @@ fn check_allowed_relay_parents() { &keystore, &signing_context_c, BackingKind::Threshold, + None, ); - let backed_candidates = vec![backed_a, backed_b, backed_c]; + let backed_candidates = vec![ + (backed_a, chain_a_assignment.1), + (backed_b, chain_b_assignment.1), + (backed_c, thread_a_assignment.1), + ]; ParaInclusion::process_candidates( &allowed_relay_parents, backed_candidates.clone(), - &[chain_a_assignment, chain_b_assignment, thread_a_assignment] - .into_iter() - .collect(), &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); }); @@ -2133,7 +2391,7 @@ fn para_upgrade_delay_scheduled_from_inclusion() { shared::Pallet::::set_active_validators_ascending(validator_public.clone()); shared::Pallet::::set_session_index(5); - let new_validation_code: ValidationCode = vec![1, 2, 3, 4, 5].into(); + let new_validation_code: ValidationCode = vec![9, 8, 7, 6, 5, 4, 3, 2, 1].into(); let new_validation_code_hash = new_validation_code.hash(); // Otherwise upgrade is no-op. @@ -2189,14 +2447,15 @@ fn para_upgrade_delay_scheduled_from_inclusion() { &keystore, &signing_context, BackingKind::Threshold, + None, ); let ProcessedCandidates { core_indices: occupied_cores, .. } = ParaInclusion::process_candidates( &allowed_relay_parents, - vec![backed_a], - &[chain_a_assignment].into_iter().collect(), + vec![(backed_a, chain_a_assignment.1)], &group_validators, + false, ) .expect("candidates scheduled, in order, and backed"); diff --git a/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs b/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs index 05c4c9c37b4d9241a64539c13a1960f93c80d7c1..53ccc35c477c093f69d06f8c9d6902e41627e0af 100644 --- a/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs +++ b/polkadot/runtime/parachains/src/paras/benchmarking/pvf_check.rs @@ -27,10 +27,10 @@ const SESSION_INDEX: SessionIndex = 1; const VALIDATOR_NUM: usize = 800; const CAUSES_NUM: usize = 100; fn validation_code() -> ValidationCode { - ValidationCode(vec![0]) + ValidationCode(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]) } fn old_validation_code() -> ValidationCode { - ValidationCode(vec![1]) + ValidationCode(vec![9, 8, 7, 6, 5, 4, 3, 2, 1]) } /// Prepares the PVF check statement and the validator signature to pass into diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index e97df8e4a2b3aaeee4e4d69c3c39262c23780588..39371391b1f09cc57d919ea85a7591689e2e04d6 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -119,7 +119,7 @@ use frame_system::pallet_prelude::*; use parity_scale_codec::{Decode, Encode}; use primitives::{ ConsensusLog, HeadData, Id as ParaId, PvfCheckStatement, SessionIndex, UpgradeGoAhead, - UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature, + UpgradeRestriction, ValidationCode, ValidationCodeHash, ValidatorSignature, MIN_CODE_SIZE, }; use scale_info::{Type, TypeInfo}; use sp_core::RuntimeDebug; @@ -679,6 +679,8 @@ pub mod pallet { PvfCheckSubjectInvalid, /// Parachain cannot currently schedule a code upgrade. CannotUpgradeCode, + /// Invalid validation code size. + InvalidCode, } /// All currently active PVF pre-checking votes. @@ -1230,6 +1232,10 @@ impl Pallet { // Check that we can schedule an upgrade at all. ensure!(Self::can_upgrade_validation_code(id), Error::::CannotUpgradeCode); let config = configuration::Pallet::::config(); + // Validation code sanity checks: + ensure!(new_code.0.len() >= MIN_CODE_SIZE as usize, Error::::InvalidCode); + ensure!(new_code.0.len() <= config.max_code_size as usize, Error::::InvalidCode); + let current_block = frame_system::Pallet::::block_number(); // Schedule the upgrade with a delay just like if a parachain triggered the upgrade. let upgrade_block = current_block.saturating_add(config.validation_upgrade_delay); @@ -1890,7 +1896,14 @@ impl Pallet { ) -> Weight { let mut weight = T::DbWeight::get().reads(1); - // Enacting this should be prevented by the `can_schedule_upgrade` + // Should be prevented by checks in `schedule_code_upgrade_external` + let new_code_len = new_code.0.len(); + if new_code_len < MIN_CODE_SIZE as usize || new_code_len > cfg.max_code_size as usize { + log::warn!(target: LOG_TARGET, "attempted to schedule an upgrade with invalid new validation code",); + return weight + } + + // Enacting this should be prevented by the `can_upgrade_validation_code` if FutureCodeHash::::contains_key(&id) { // This branch should never be reached. Signalling an upgrade is disallowed for a para // that already has one upgrade scheduled. diff --git a/polkadot/runtime/parachains/src/paras/tests.rs b/polkadot/runtime/parachains/src/paras/tests.rs index cca200c2765e361120b4c5ea8db3dfc7c82bdc13..24ea919ec8754a50d6fb9507739a48c8a3eb1120 100644 --- a/polkadot/runtime/parachains/src/paras/tests.rs +++ b/polkadot/runtime/parachains/src/paras/tests.rs @@ -67,6 +67,16 @@ fn submit_super_majority_pvf_votes( .for_each(sign_and_include_pvf_check_statement); } +fn test_validation_code_1() -> ValidationCode { + let validation_code = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; + ValidationCode(validation_code) +} + +fn test_validation_code_2() -> ValidationCode { + let validation_code = vec![9, 8, 7, 6, 5, 4, 3, 2, 1]; + ValidationCode(validation_code) +} + fn run_to_block(to: BlockNumber, new_session: Option>) { let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); for validator in VALIDATORS.iter() { @@ -284,7 +294,7 @@ fn para_past_code_pruning_in_initialize() { let id = ParaId::from(0u32); let at_block: BlockNumber = 10; let included_block: BlockNumber = 12; - let validation_code = ValidationCode(vec![4, 5, 6]); + let validation_code = test_validation_code_2(); Paras::increase_code_ref(&validation_code.hash(), &validation_code); PastCodeHash::::insert(&(id, at_block), &validation_code.hash()); @@ -377,8 +387,8 @@ fn note_past_code_sets_up_pruning_correctly() { let id_a = ParaId::from(0u32); let id_b = ParaId::from(1u32); - Paras::note_past_code(id_a, 10, 12, ValidationCode(vec![1, 2, 3]).hash()); - Paras::note_past_code(id_b, 20, 23, ValidationCode(vec![4, 5, 6]).hash()); + Paras::note_past_code(id_a, 10, 12, test_validation_code_1().hash()); + Paras::note_past_code(id_b, 20, 23, test_validation_code_2().hash()); assert_eq!(PastCodePruning::::get(), vec![(id_a, 12), (id_b, 23)]); assert_eq!( @@ -398,7 +408,7 @@ fn code_upgrade_applied_after_delay() { let validation_upgrade_delay = 5; let validation_upgrade_cooldown = 10; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -425,7 +435,7 @@ fn code_upgrade_applied_after_delay() { check_code_is_stored(&original_code); let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -516,7 +526,7 @@ fn code_upgrade_applied_without_setting_go_ahead_signal() { let validation_upgrade_delay = 5; let validation_upgrade_cooldown = 10; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -543,7 +553,7 @@ fn code_upgrade_applied_without_setting_go_ahead_signal() { check_code_is_stored(&original_code); let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -637,7 +647,7 @@ fn code_upgrade_applied_after_delay_even_when_late() { let validation_upgrade_delay = 5; let validation_upgrade_cooldown = 10; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -662,7 +672,7 @@ fn code_upgrade_applied_after_delay_even_when_late() { new_test_ext(genesis_config).execute_with(|| { let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -750,8 +760,8 @@ fn submit_code_change_when_not_allowed_is_err() { new_test_ext(genesis_config).execute_with(|| { let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); - let newer_code = ValidationCode(vec![4, 5, 6, 7]); + let new_code = test_validation_code_1(); + let newer_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -832,8 +842,8 @@ fn upgrade_restriction_elapsed_doesnt_mean_can_upgrade() { new_test_ext(genesis_config).execute_with(|| { let para_id = 0u32.into(); - let new_code = ValidationCode(vec![4, 5, 6]); - let newer_code = ValidationCode(vec![4, 5, 6, 7]); + let new_code = test_validation_code_1(); + let newer_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -880,7 +890,7 @@ fn full_parachain_cleanup_storage() { let code_retention_period = 20; let validation_upgrade_delay = 1 + 5; - let original_code = ValidationCode(vec![1, 2, 3]); + let original_code = test_validation_code_1(); let paras = vec![( 0u32.into(), ParaGenesisArgs { @@ -910,7 +920,7 @@ fn full_parachain_cleanup_storage() { check_code_is_stored(&original_code); let para_id = ParaId::from(0); - let new_code = ValidationCode(vec![4, 5, 6]); + let new_code = test_validation_code_2(); // Wait for at least one session change to set active validators. const EXPECTED_SESSION: SessionIndex = 1; @@ -993,8 +1003,8 @@ fn full_parachain_cleanup_storage() { fn cannot_offboard_ongoing_pvf_check() { let para_id = ParaId::from(0); - let existing_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![3, 2, 1].into(); + let existing_code = test_validation_code_1(); + let new_code = test_validation_code_2(); let paras = vec![( para_id, @@ -1152,7 +1162,7 @@ fn code_hash_at_returns_up_to_end_of_code_retention_period() { ParaGenesisArgs { para_kind: ParaKind::Parachain, genesis_head: dummy_head_data(), - validation_code: vec![1, 2, 3].into(), + validation_code: test_validation_code_1(), }, )]; @@ -1174,8 +1184,8 @@ fn code_hash_at_returns_up_to_end_of_code_retention_period() { const EXPECTED_SESSION: SessionIndex = 1; let para_id = ParaId::from(0); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![4, 5, 6].into(); + let old_code = test_validation_code_1(); + let new_code = test_validation_code_2(); Paras::schedule_code_upgrade( para_id, new_code.clone(), @@ -1219,7 +1229,7 @@ fn code_hash_at_returns_up_to_end_of_code_retention_period() { #[test] fn code_ref_is_cleaned_correctly() { new_test_ext(Default::default()).execute_with(|| { - let code: ValidationCode = vec![1, 2, 3].into(); + let code = test_validation_code_1(); Paras::increase_code_ref(&code.hash(), &code); Paras::increase_code_ref(&code.hash(), &code); @@ -1244,8 +1254,8 @@ fn pvf_check_coalescing_onboarding_and_upgrade() { let a = ParaId::from(111); let b = ParaId::from(222); - let existing_code: ValidationCode = vec![1, 2, 3].into(); - let validation_code: ValidationCode = vec![3, 2, 1].into(); + let existing_code = test_validation_code_1(); + let validation_code = test_validation_code_2(); let paras = vec![( a, @@ -1320,7 +1330,7 @@ fn pvf_check_coalescing_onboarding_and_upgrade() { fn pvf_check_onboarding_reject_on_expiry() { let pvf_voting_ttl = 2; let a = ParaId::from(111); - let validation_code: ValidationCode = vec![3, 2, 1].into(); + let validation_code = test_validation_code_1(); let genesis_config = MockGenesisConfig { configuration: crate::configuration::GenesisConfig { @@ -1368,8 +1378,8 @@ fn pvf_check_onboarding_reject_on_expiry() { #[test] fn pvf_check_upgrade_reject() { let a = ParaId::from(111); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![3, 2, 1].into(); + let old_code = test_validation_code_1(); + let new_code = test_validation_code_2(); let paras = vec![( a, @@ -1437,8 +1447,8 @@ fn pvf_check_upgrade_reject() { #[test] fn pvf_check_submit_vote() { - let code_a: ValidationCode = vec![3, 2, 1].into(); - let code_b: ValidationCode = vec![1, 2, 3].into(); + let code_a = test_validation_code_1(); + let code_b = test_validation_code_2(); let check = |stmt: PvfCheckStatement| -> (Result<_, _>, Result<_, _>) { let validators = &[ @@ -1554,8 +1564,8 @@ fn pvf_check_submit_vote() { #[test] fn include_pvf_check_statement_refunds_weight() { let a = ParaId::from(111); - let old_code: ValidationCode = vec![1, 2, 3].into(); - let new_code: ValidationCode = vec![3, 2, 1].into(); + let old_code = test_validation_code_1(); + let new_code = test_validation_code_2(); let paras = vec![( a, @@ -1620,7 +1630,7 @@ fn include_pvf_check_statement_refunds_weight() { fn add_trusted_validation_code_inserts_with_no_users() { // This test is to ensure that trusted validation code is inserted into the storage // with the reference count equal to 0. - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code( RuntimeOrigin::root(), @@ -1634,7 +1644,7 @@ fn add_trusted_validation_code_inserts_with_no_users() { fn add_trusted_validation_code_idempotent() { // This test makes sure that calling add_trusted_validation_code twice with the same // parameters is a no-op. - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code( RuntimeOrigin::root(), @@ -1653,7 +1663,7 @@ fn add_trusted_validation_code_idempotent() { fn poke_unused_validation_code_removes_code_cleanly() { // This test makes sure that calling poke_unused_validation_code with a code that is currently // in the storage but has no users will remove it cleanly from the storage. - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code( RuntimeOrigin::root(), @@ -1672,7 +1682,7 @@ fn poke_unused_validation_code_removes_code_cleanly() { #[test] fn poke_unused_validation_code_doesnt_remove_code_with_users() { let para_id = 100.into(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { // First we add the code to the storage. assert_ok!(Paras::add_trusted_validation_code( @@ -1708,7 +1718,7 @@ fn increase_code_ref_doesnt_have_allergy_on_add_trusted_validation_code() { // to a disaster. // NOTE that this test is extra paranoid, as it is not really possible to hit // `decrease_code_ref` without calling `increase_code_ref` first. - let code = ValidationCode(vec![1, 2, 3]); + let code = test_validation_code_1(); new_test_ext(Default::default()).execute_with(|| { assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), code.clone())); @@ -1732,7 +1742,7 @@ fn add_trusted_validation_code_insta_approval() { // `add_trusted_validation_code` and uses the `CodeByHash::contains_key` which is what // `add_trusted_validation_code` uses. let para_id = 100.into(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); let validation_upgrade_delay = 25; let minimum_validation_upgrade_delay = 2; let genesis_config = MockGenesisConfig { @@ -1779,7 +1789,7 @@ fn add_trusted_validation_code_enacts_existing_pvf_vote() { // already going through PVF pre-checking voting will conclude the voting and enact the // code upgrade. let para_id = 100.into(); - let validation_code = ValidationCode(vec![1, 2, 3]); + let validation_code = test_validation_code_1(); let validation_upgrade_delay = 25; let minimum_validation_upgrade_delay = 2; let genesis_config = MockGenesisConfig { @@ -1868,7 +1878,7 @@ fn verify_para_head_is_externally_accessible() { #[test] fn most_recent_context() { - let validation_code: ValidationCode = vec![1, 2, 3].into(); + let validation_code = test_validation_code_1(); let genesis_config = MockGenesisConfig::default(); diff --git a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs index 0f6b23ae1b39213f8afbd37798f8f7f31a024cf9..ad3fa8e0dc712b64141231250e251498803c52b1 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/benchmarking.rs @@ -120,7 +120,7 @@ benchmarks! { // with `v` validity votes. // let votes = v as usize; let votes = min(scheduler::Pallet::::group_validators(GroupIndex::from(0)).unwrap().len(), v as usize); - assert_eq!(benchmark.backed_candidates.get(0).unwrap().validity_votes.len(), votes); + assert_eq!(benchmark.backed_candidates.get(0).unwrap().validity_votes().len(), votes); benchmark.bitfields.clear(); benchmark.disputes.clear(); @@ -177,7 +177,7 @@ benchmarks! { // There is 1 backed assert_eq!(benchmark.backed_candidates.len(), 1); assert_eq!( - benchmark.backed_candidates.get(0).unwrap().validity_votes.len(), + benchmark.backed_candidates.get(0).unwrap().validity_votes().len(), votes, ); diff --git a/polkadot/runtime/parachains/src/paras_inherent/mod.rs b/polkadot/runtime/parachains/src/paras_inherent/mod.rs index 81e092f0a991cd216bac3c1d8e48ae8bcea59144..cebf858c24ab05a68e3192e252c3038895142065 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/mod.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/mod.rs @@ -43,15 +43,14 @@ use frame_support::{ use frame_system::pallet_prelude::*; use pallet_babe::{self, ParentBlockRandomness}; use primitives::{ - effective_minimum_backing_votes, BackedCandidate, CandidateHash, CandidateReceipt, - CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CoreIndex, DisputeStatementSet, - InherentData as ParachainsInherentData, MultiDisputeStatementSet, ScrapedOnChainVotes, - SessionIndex, SignedAvailabilityBitfields, SigningContext, UncheckedSignedAvailabilityBitfield, - UncheckedSignedAvailabilityBitfields, ValidatorId, ValidatorIndex, ValidityAttestation, - PARACHAINS_INHERENT_IDENTIFIER, + effective_minimum_backing_votes, vstaging::node_features::FeatureIndex, BackedCandidate, + CandidateHash, CandidateReceipt, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, + CoreIndex, DisputeStatementSet, InherentData as ParachainsInherentData, + MultiDisputeStatementSet, ScrapedOnChainVotes, SessionIndex, SignedAvailabilityBitfields, + SigningContext, UncheckedSignedAvailabilityBitfield, UncheckedSignedAvailabilityBitfields, + ValidatorId, ValidatorIndex, ValidityAttestation, PARACHAINS_INHERENT_IDENTIFIER, }; use rand::{seq::SliceRandom, SeedableRng}; - use scale_info::TypeInfo; use sp_runtime::traits::{Header as HeaderT, One}; use sp_std::{ @@ -145,6 +144,10 @@ pub mod pallet { DisputeInvalid, /// A candidate was backed by a disabled validator BackedByDisabled, + /// A candidate was backed even though the paraid was not scheduled. + BackedOnUnscheduledCore, + /// Too many candidates supplied. + UnscheduledCandidate, } /// Whether the paras inherent was included within this block. @@ -585,25 +588,39 @@ impl Pallet { let freed = collect_all_freed_cores::(freed_concluded.iter().cloned()); >::free_cores_and_fill_claimqueue(freed, now); - let scheduled = >::scheduled_paras() - .map(|(core_idx, para_id)| (para_id, core_idx)) - .collect(); METRICS.on_candidates_processed_total(backed_candidates.len() as u64); - let SanitizedBackedCandidates { backed_candidates, votes_from_disabled_were_dropped } = - sanitize_backed_candidates::( - backed_candidates, - &allowed_relay_parents, - |candidate_idx: usize, - backed_candidate: &BackedCandidate<::Hash>| - -> bool { - let para_id = backed_candidate.descriptor().para_id; - let prev_context = >::para_most_recent_context(para_id); - let check_ctx = CandidateCheckContext::::new(prev_context); - - // never include a concluded-invalid candidate - current_concluded_invalid_disputes.contains(&backed_candidate.hash()) || + let core_index_enabled = configuration::Pallet::::config() + .node_features + .get(FeatureIndex::ElasticScalingMVP as usize) + .map(|b| *b) + .unwrap_or(false); + + let mut scheduled: BTreeMap> = BTreeMap::new(); + let mut total_scheduled_cores = 0; + + for (core_idx, para_id) in >::scheduled_paras() { + total_scheduled_cores += 1; + scheduled.entry(para_id).or_default().insert(core_idx); + } + + let SanitizedBackedCandidates { + backed_candidates_with_core, + votes_from_disabled_were_dropped, + dropped_unscheduled_candidates, + } = sanitize_backed_candidates::( + backed_candidates, + &allowed_relay_parents, + |candidate_idx: usize, + backed_candidate: &BackedCandidate<::Hash>| + -> bool { + let para_id = backed_candidate.descriptor().para_id; + let prev_context = >::para_most_recent_context(para_id); + let check_ctx = CandidateCheckContext::::new(prev_context); + + // never include a concluded-invalid candidate + current_concluded_invalid_disputes.contains(&backed_candidate.hash()) || // Instead of checking the candidates with code upgrades twice // move the checking up here and skip it in the training wheels fallback. // That way we avoid possible duplicate checks while assuring all @@ -611,13 +628,19 @@ impl Pallet { // // NOTE: this is the only place where we check the relay-parent. check_ctx - .verify_backed_candidate(&allowed_relay_parents, candidate_idx, backed_candidate) + .verify_backed_candidate(&allowed_relay_parents, candidate_idx, backed_candidate.candidate()) .is_err() - }, - &scheduled, - ); + }, + scheduled, + core_index_enabled, + ); + + ensure!( + backed_candidates_with_core.len() <= total_scheduled_cores, + Error::::UnscheduledCandidate + ); - METRICS.on_candidates_sanitized(backed_candidates.len() as u64); + METRICS.on_candidates_sanitized(backed_candidates_with_core.len() as u64); // In `Enter` context (invoked during execution) there should be no backing votes from // disabled validators because they should have been filtered out during inherent data @@ -626,15 +649,22 @@ impl Pallet { ensure!(!votes_from_disabled_were_dropped, Error::::BackedByDisabled); } + // In `Enter` context (invoked during execution) we shouldn't have filtered any candidates + // due to a para not being scheduled. They have been filtered during inherent data + // preparation (`ProvideInherent` context). Abort in such cases. + if context == ProcessInherentDataContext::Enter { + ensure!(!dropped_unscheduled_candidates, Error::::BackedOnUnscheduledCore); + } + // Process backed candidates according to scheduled cores. let inclusion::ProcessedCandidates::< as HeaderT>::Hash> { core_indices: occupied, candidate_receipt_with_backing_validator_indices, } = >::process_candidates( &allowed_relay_parents, - backed_candidates.clone(), - &scheduled, + backed_candidates_with_core.clone(), >::group_validators, + core_index_enabled, )?; // Note which of the scheduled cores were actually occupied by a backed candidate. >::occupied(occupied.into_iter().map(|e| (e.0, e.1)).collect()); @@ -651,8 +681,15 @@ impl Pallet { let bitfields = bitfields.into_iter().map(|v| v.into_unchecked()).collect(); - let processed = - ParachainsInherentData { bitfields, backed_candidates, disputes, parent_header }; + let processed = ParachainsInherentData { + bitfields, + backed_candidates: backed_candidates_with_core + .into_iter() + .map(|(candidate, _)| candidate) + .collect(), + disputes, + parent_header, + }; Ok((processed, Some(all_weight_after).into())) } } @@ -774,7 +811,7 @@ fn apply_weight_limit( .iter() .enumerate() .filter_map(|(idx, candidate)| { - candidate.candidate.commitments.new_validation_code.as_ref().map(|_code| idx) + candidate.candidate().commitments.new_validation_code.as_ref().map(|_code| idx) }) .collect::>(); @@ -916,16 +953,22 @@ pub(crate) fn sanitize_bitfields( // Result from `sanitize_backed_candidates` #[derive(Debug, PartialEq)] struct SanitizedBackedCandidates { - // Sanitized backed candidates. The `Vec` is sorted according to the occupied core index. - backed_candidates: Vec>, + // Sanitized backed candidates along with the assigned core. The `Vec` is sorted according to + // the occupied core index. + backed_candidates_with_core: Vec<(BackedCandidate, CoreIndex)>, // Set to true if any votes from disabled validators were dropped from the input. votes_from_disabled_were_dropped: bool, + // Set to true if any candidates were dropped due to filtering done in + // `map_candidates_to_cores` + dropped_unscheduled_candidates: bool, } /// Filter out: /// 1. any candidates that have a concluded invalid dispute -/// 2. all backing votes from disabled validators -/// 3. any candidates that end up with less than `effective_minimum_backing_votes` backing votes +/// 2. any unscheduled candidates, as well as candidates whose paraid has multiple cores assigned +/// but have no injected core index. +/// 3. all backing votes from disabled validators +/// 4. any candidates that end up with less than `effective_minimum_backing_votes` backing votes /// /// `scheduled` follows the same naming scheme as provided in the /// guide: Currently `free` but might become `occupied`. @@ -944,7 +987,8 @@ fn sanitize_backed_candidates< mut backed_candidates: Vec>, allowed_relay_parents: &AllowedRelayParentsTracker>, mut candidate_has_concluded_invalid_dispute_or_is_invalid: F, - scheduled: &BTreeMap, + scheduled: BTreeMap>, + core_index_enabled: bool, ) -> SanitizedBackedCandidates { // Remove any candidates that were concluded invalid. // This does not assume sorting. @@ -952,22 +996,23 @@ fn sanitize_backed_candidates< !candidate_has_concluded_invalid_dispute_or_is_invalid(candidate_idx, backed_candidate) }); - // Assure the backed candidate's `ParaId`'s core is free. - // This holds under the assumption that `Scheduler::schedule` is called _before_. - // We don't check the relay-parent because this is done in the closure when - // constructing the inherent and during actual processing otherwise. - - backed_candidates.retain(|backed_candidate| { - let desc = backed_candidate.descriptor(); + let initial_candidate_count = backed_candidates.len(); + // Map candidates to scheduled cores. Filter out any unscheduled candidates. + let mut backed_candidates_with_core = map_candidates_to_cores::( + &allowed_relay_parents, + scheduled, + core_index_enabled, + backed_candidates, + ); - scheduled.get(&desc.para_id).is_some() - }); + let dropped_unscheduled_candidates = + initial_candidate_count != backed_candidates_with_core.len(); // Filter out backing statements from disabled validators - let dropped_disabled = filter_backed_statements_from_disabled_validators::( - &mut backed_candidates, + let votes_from_disabled_were_dropped = filter_backed_statements_from_disabled_validators::( + &mut backed_candidates_with_core, &allowed_relay_parents, - scheduled, + core_index_enabled, ); // Sort the `Vec` last, once there is a guarantee that these @@ -975,14 +1020,12 @@ fn sanitize_backed_candidates< // but more importantly are scheduled for a free core. // This both avoids extra work for obviously invalid candidates, // but also allows this to be done in place. - backed_candidates.sort_by(|x, y| { - // Never panics, since we filtered all panic arguments out in the previous `fn retain`. - scheduled[&x.descriptor().para_id].cmp(&scheduled[&y.descriptor().para_id]) - }); + backed_candidates_with_core.sort_by(|(_x, core_x), (_y, core_y)| core_x.cmp(&core_y)); SanitizedBackedCandidates { - backed_candidates, - votes_from_disabled_were_dropped: dropped_disabled, + dropped_unscheduled_candidates, + votes_from_disabled_were_dropped, + backed_candidates_with_core, } } @@ -1071,9 +1114,12 @@ fn limit_and_sanitize_disputes< // few more sanity checks. Returns `true` if at least one statement is removed and `false` // otherwise. fn filter_backed_statements_from_disabled_validators( - backed_candidates: &mut Vec::Hash>>, + backed_candidates_with_core: &mut Vec<( + BackedCandidate<::Hash>, + CoreIndex, + )>, allowed_relay_parents: &AllowedRelayParentsTracker>, - scheduled: &BTreeMap, + core_index_enabled: bool, ) -> bool { let disabled_validators = BTreeSet::<_>::from_iter(shared::Pallet::::disabled_validators().into_iter()); @@ -1083,7 +1129,7 @@ fn filter_backed_statements_from_disabled_validators *core_idx, - None => { - log::debug!(target: LOG_TARGET, "Can't get core idx of a backed candidate for para id {:?}. Dropping the candidate.", bc.descriptor().para_id); - return false - } - }; + backed_candidates_with_core.retain_mut(|(bc, core_idx)| { + let (validator_indices, maybe_core_index) = bc.validator_indices_and_core_index(core_index_enabled); + let mut validator_indices = BitVec::<_>::from(validator_indices); // Get relay parent block number of the candidate. We need this to get the group index assigned to this core at this block number let relay_parent_block_number = match allowed_relay_parents @@ -1116,7 +1156,7 @@ fn filter_backed_statements_from_disabled_validators>::group_assigned_to_core( - core_idx, + *core_idx, relay_parent_block_number + One::one(), ) { Some(group_idx) => group_idx, @@ -1138,12 +1178,15 @@ fn filter_backed_statements_from_disabled_validators::from_iter(validator_group.iter().map(|idx| disabled_validators.contains(idx))); // The indices of statements from disabled validators in `BackedCandidate`. We have to drop these. - let indices_to_drop = disabled_indices.clone() & &bc.validator_indices; + let indices_to_drop = disabled_indices.clone() & &validator_indices; // Apply the bitmask to drop the disabled validator from `validator_indices` - bc.validator_indices &= !disabled_indices; + validator_indices &= !disabled_indices; + // Update the backed candidate + bc.set_validator_indices_and_core_index(validator_indices, maybe_core_index); + // Remove the corresponding votes from `validity_votes` for idx in indices_to_drop.iter_ones().rev() { - bc.validity_votes.remove(idx); + bc.validity_votes_mut().remove(idx); } // If at least one statement was dropped we need to return `true` @@ -1154,10 +1197,9 @@ fn filter_backed_statements_from_disabled_validators( + allowed_relay_parents: &AllowedRelayParentsTracker>, + mut scheduled: BTreeMap>, + core_index_enabled: bool, + candidates: Vec>, +) -> Vec<(BackedCandidate, CoreIndex)> { + let mut backed_candidates_with_core = Vec::with_capacity(candidates.len()); + + // We keep a candidate if the parachain has only one core assigned or if + // a core index is provided by block author and it's indeed scheduled. + for backed_candidate in candidates { + let maybe_injected_core_index = get_injected_core_index::( + allowed_relay_parents, + &backed_candidate, + core_index_enabled, + ); + + let scheduled_cores = scheduled.get_mut(&backed_candidate.descriptor().para_id); + // Candidates without scheduled cores are silently filtered out. + if let Some(scheduled_cores) = scheduled_cores { + if let Some(core_idx) = maybe_injected_core_index { + if scheduled_cores.contains(&core_idx) { + scheduled_cores.remove(&core_idx); + backed_candidates_with_core.push((backed_candidate, core_idx)); + } + } else if scheduled_cores.len() == 1 { + backed_candidates_with_core + .push((backed_candidate, scheduled_cores.pop_first().expect("Length is 1"))); + } + } + } + + backed_candidates_with_core +} + +fn get_injected_core_index( + allowed_relay_parents: &AllowedRelayParentsTracker>, + candidate: &BackedCandidate, + core_index_enabled: bool, +) -> Option { + // After stripping the 8 bit extensions, the `validator_indices` field length is expected + // to be equal to backing group size. If these don't match, the `CoreIndex` is badly encoded, + // or not supported. + let (validator_indices, maybe_core_idx) = + candidate.validator_indices_and_core_index(core_index_enabled); + + let Some(core_idx) = maybe_core_idx else { return None }; + + let relay_parent_block_number = + match allowed_relay_parents.acquire_info(candidate.descriptor().relay_parent, None) { + Some((_, block_num)) => block_num, + None => { + log::debug!( + target: LOG_TARGET, + "Relay parent {:?} for candidate {:?} is not in the allowed relay parents. Dropping the candidate.", + candidate.descriptor().relay_parent, + candidate.candidate().hash(), + ); + return None + }, + }; + + // Get the backing group of the candidate backed at `core_idx`. + let group_idx = match >::group_assigned_to_core( + core_idx, + relay_parent_block_number + One::one(), + ) { + Some(group_idx) => group_idx, + None => { + log::debug!( + target: LOG_TARGET, + "Can't get the group index for core idx {:?}. Dropping the candidate {:?}.", + core_idx, + candidate.candidate().hash(), + ); + return None + }, + }; + + let group_validators = match >::group_validators(group_idx) { + Some(validators) => validators, + None => return None, + }; + + if group_validators.len() == validator_indices.len() { + Some(core_idx) + } else { + None + } } diff --git a/polkadot/runtime/parachains/src/paras_inherent/tests.rs b/polkadot/runtime/parachains/src/paras_inherent/tests.rs index 6f3eac35685a82b32c69b27e5379620988c956b7..defb2f4404f56c4266c653c4c3242bb68702649b 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/tests.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/tests.rs @@ -26,7 +26,10 @@ mod enter { use crate::{ builder::{Bench, BenchBuilder}, mock::{mock_assigner, new_test_ext, BlockLength, BlockWeights, MockGenesisConfig, Test}, - scheduler::common::Assignment, + scheduler::{ + common::{Assignment, AssignmentProvider, AssignmentProviderConfig}, + ParasEntry, + }, }; use assert_matches::assert_matches; use frame_support::assert_ok; @@ -697,6 +700,25 @@ mod enter { 2 ); + // One core was scheduled. We should put the assignment back, before calling enter(). + let now = >::block_number() + 1; + let used_cores = 5; + let cores = (0..used_cores) + .into_iter() + .map(|i| { + let AssignmentProviderConfig { ttl, .. } = + scheduler::Pallet::::assignment_provider_config(CoreIndex(i)); + // Load an assignment into provider so that one is present to pop + let assignment = + ::AssignmentProvider::get_mock_assignment( + CoreIndex(i), + ParaId::from(i), + ); + (CoreIndex(i), [ParasEntry::new(assignment, now + ttl)].into()) + }) + .collect(); + scheduler::ClaimQueue::::set(cores); + assert_ok!(Pallet::::enter( frame_system::RawOrigin::None.into(), limit_inherent_data, @@ -980,6 +1002,7 @@ mod sanitizers { AvailabilityBitfield, GroupIndex, Hash, Id as ParaId, SignedAvailabilityBitfield, ValidatorIndex, }; + use rstest::rstest; use sp_core::crypto::UncheckedFrom; use crate::mock::Test; @@ -1238,12 +1261,13 @@ mod sanitizers { // Backed candidates and scheduled parachains used for `sanitize_backed_candidates` testing struct TestData { backed_candidates: Vec, - scheduled_paras: BTreeMap, + all_backed_candidates_with_core: Vec<(BackedCandidate, CoreIndex)>, + scheduled_paras: BTreeMap>, } // Generate test data for the candidates and assert that the evnironment is set as expected // (check the comments for details) - fn get_test_data() -> TestData { + fn get_test_data(core_index_enabled: bool) -> TestData { const RELAY_PARENT_NUM: u32 = 3; // Add the relay parent to `shared` pallet. Otherwise some code (e.g. filtering backing @@ -1285,9 +1309,14 @@ mod sanitizers { shared::Pallet::::set_active_validators_ascending(validator_ids); // Two scheduled parachains - ParaId(1) on CoreIndex(0) and ParaId(2) on CoreIndex(1) - let scheduled = (0_usize..2) + let scheduled: BTreeMap> = (0_usize..2) .into_iter() - .map(|idx| (ParaId::from(1_u32 + idx as u32), CoreIndex::from(idx as u32))) + .map(|idx| { + ( + ParaId::from(1_u32 + idx as u32), + [CoreIndex::from(idx as u32)].into_iter().collect(), + ) + }) .collect::>(); // Set the validator groups in `scheduler` @@ -1301,7 +1330,7 @@ mod sanitizers { ( CoreIndex::from(0), VecDeque::from([ParasEntry::new( - Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(1) }, + Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(0) }, RELAY_PARENT_NUM, )]), ), @@ -1319,12 +1348,12 @@ mod sanitizers { match group_index { group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), - _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), + _ => panic!("Group index out of bounds"), } .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) }; - // Two backed candidates from each parachain + // One backed candidate from each parachain let backed_candidates = (0_usize..2) .into_iter() .map(|idx0| { @@ -1348,6 +1377,7 @@ mod sanitizers { &keystore, &signing_context, BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(idx0 as u32)), ); backed }) @@ -1369,13 +1399,373 @@ mod sanitizers { ] ); - TestData { backed_candidates, scheduled_paras: scheduled } + let all_backed_candidates_with_core = backed_candidates + .iter() + .map(|candidate| { + // Only one entry for this test data. + ( + candidate.clone(), + scheduled + .get(&candidate.descriptor().para_id) + .unwrap() + .first() + .copied() + .unwrap(), + ) + }) + .collect(); + + TestData { + backed_candidates, + scheduled_paras: scheduled, + all_backed_candidates_with_core, + } } - #[test] - fn happy_path() { + // Generate test data for the candidates and assert that the evnironment is set as expected + // (check the comments for details) + // Para 1 scheduled on core 0 and core 1. Two candidates are supplied. + // Para 2 scheduled on cores 2 and 3. One candidate supplied. + // Para 3 scheduled on core 4. One candidate supplied. + // Para 4 scheduled on core 5. Two candidates supplied. + // Para 5 scheduled on core 6. No candidates supplied. + fn get_test_data_multiple_cores_per_para(core_index_enabled: bool) -> TestData { + const RELAY_PARENT_NUM: u32 = 3; + + // Add the relay parent to `shared` pallet. Otherwise some code (e.g. filtering backing + // votes) won't behave correctly + shared::Pallet::::add_allowed_relay_parent( + default_header().hash(), + Default::default(), + RELAY_PARENT_NUM, + 1, + ); + + let header = default_header(); + let relay_parent = header.hash(); + let session_index = SessionIndex::from(0_u32); + + let keystore = LocalKeystore::in_memory(); + let keystore = Arc::new(keystore) as KeystorePtr; + let signing_context = SigningContext { parent_hash: relay_parent, session_index }; + + let validators = vec![ + keyring::Sr25519Keyring::Alice, + keyring::Sr25519Keyring::Bob, + keyring::Sr25519Keyring::Charlie, + keyring::Sr25519Keyring::Dave, + keyring::Sr25519Keyring::Eve, + keyring::Sr25519Keyring::Ferdie, + keyring::Sr25519Keyring::One, + ]; + for validator in validators.iter() { + Keystore::sr25519_generate_new( + &*keystore, + PARACHAIN_KEY_TYPE_ID, + Some(&validator.to_seed()), + ) + .unwrap(); + } + + // Set active validators in `shared` pallet + let validator_ids = + validators.iter().map(|v| v.public().into()).collect::>(); + shared::Pallet::::set_active_validators_ascending(validator_ids); + + // Set the validator groups in `scheduler` + scheduler::Pallet::::set_validator_groups(vec![ + vec![ValidatorIndex(0)], + vec![ValidatorIndex(1)], + vec![ValidatorIndex(2)], + vec![ValidatorIndex(3)], + vec![ValidatorIndex(4)], + vec![ValidatorIndex(5)], + vec![ValidatorIndex(6)], + ]); + + // Update scheduler's claimqueue with the parachains + scheduler::Pallet::::set_claimqueue(BTreeMap::from([ + ( + CoreIndex::from(0), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(0) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(1), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 1.into(), core_index: CoreIndex(1) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(2), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 2.into(), core_index: CoreIndex(2) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(3), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 2.into(), core_index: CoreIndex(3) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(4), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 3.into(), core_index: CoreIndex(4) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(5), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 4.into(), core_index: CoreIndex(5) }, + RELAY_PARENT_NUM, + )]), + ), + ( + CoreIndex::from(6), + VecDeque::from([ParasEntry::new( + Assignment::Pool { para_id: 5.into(), core_index: CoreIndex(6) }, + RELAY_PARENT_NUM, + )]), + ), + ])); + + // Callback used for backing candidates + let group_validators = |group_index: GroupIndex| { + match group_index { + group_index if group_index == GroupIndex::from(0) => Some(vec![0]), + group_index if group_index == GroupIndex::from(1) => Some(vec![1]), + group_index if group_index == GroupIndex::from(2) => Some(vec![2]), + group_index if group_index == GroupIndex::from(3) => Some(vec![3]), + group_index if group_index == GroupIndex::from(4) => Some(vec![4]), + group_index if group_index == GroupIndex::from(5) => Some(vec![5]), + group_index if group_index == GroupIndex::from(6) => Some(vec![6]), + + _ => panic!("Group index out of bounds"), + } + .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) + }; + + let mut backed_candidates = vec![]; + let mut all_backed_candidates_with_core = vec![]; + + // Para 1 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(1), + relay_parent, + pov_hash: Hash::repeat_byte(1 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed: BackedCandidate = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(0 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(0 as u32)), + ); + backed_candidates.push(backed.clone()); + if core_index_enabled { + all_backed_candidates_with_core.push((backed, CoreIndex(0))); + } + + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(1), + relay_parent, + pov_hash: Hash::repeat_byte(2 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(1 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(1 as u32)), + ); + backed_candidates.push(backed.clone()); + if core_index_enabled { + all_backed_candidates_with_core.push((backed, CoreIndex(1))); + } + } + + // Para 2 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(2), + relay_parent, + pov_hash: Hash::repeat_byte(3 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(2 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(2 as u32)), + ); + backed_candidates.push(backed.clone()); + if core_index_enabled { + all_backed_candidates_with_core.push((backed, CoreIndex(2))); + } + } + + // Para 3 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(3), + relay_parent, + pov_hash: Hash::repeat_byte(4 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(4 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(4 as u32)), + ); + backed_candidates.push(backed.clone()); + all_backed_candidates_with_core.push((backed, CoreIndex(4))); + } + + // Para 4 + { + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(4), + relay_parent, + pov_hash: Hash::repeat_byte(5 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(5 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + None, + ); + backed_candidates.push(backed.clone()); + all_backed_candidates_with_core.push((backed, CoreIndex(5))); + + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(4), + relay_parent, + pov_hash: Hash::repeat_byte(6 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(5 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + core_index_enabled.then_some(CoreIndex(5 as u32)), + ); + backed_candidates.push(backed.clone()); + } + + // No candidate for para 5. + + // State sanity checks + assert_eq!( + >::scheduled_paras().collect::>(), + vec![ + (CoreIndex(0), ParaId::from(1)), + (CoreIndex(1), ParaId::from(1)), + (CoreIndex(2), ParaId::from(2)), + (CoreIndex(3), ParaId::from(2)), + (CoreIndex(4), ParaId::from(3)), + (CoreIndex(5), ParaId::from(4)), + (CoreIndex(6), ParaId::from(5)), + ] + ); + let mut scheduled: BTreeMap> = BTreeMap::new(); + for (core_idx, para_id) in >::scheduled_paras() { + scheduled.entry(para_id).or_default().insert(core_idx); + } + + assert_eq!( + shared::Pallet::::active_validator_indices(), + vec![ + ValidatorIndex(0), + ValidatorIndex(1), + ValidatorIndex(2), + ValidatorIndex(3), + ValidatorIndex(4), + ValidatorIndex(5), + ValidatorIndex(6), + ] + ); + + TestData { + backed_candidates, + scheduled_paras: scheduled, + all_backed_candidates_with_core, + } + } + + #[rstest] + #[case(false)] + #[case(true)] + fn happy_path(#[case] core_index_enabled: bool) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { backed_candidates, scheduled_paras: scheduled } = get_test_data(); + let TestData { + backed_candidates, + all_backed_candidates_with_core, + scheduled_paras: scheduled, + } = get_test_data(core_index_enabled); let has_concluded_invalid = |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; @@ -1385,47 +1775,95 @@ mod sanitizers { backed_candidates.clone(), &>::allowed_relay_parents(), has_concluded_invalid, - &scheduled + scheduled, + core_index_enabled ), SanitizedBackedCandidates { - backed_candidates, - votes_from_disabled_were_dropped: false + backed_candidates_with_core: all_backed_candidates_with_core, + votes_from_disabled_were_dropped: false, + dropped_unscheduled_candidates: false } ); + }); + } + + #[rstest] + #[case(false)] + #[case(true)] + fn test_with_multiple_cores_per_para(#[case] core_index_enabled: bool) { + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let TestData { + backed_candidates, + all_backed_candidates_with_core: expected_all_backed_candidates_with_core, + scheduled_paras: scheduled, + } = get_test_data_multiple_cores_per_para(core_index_enabled); + + let has_concluded_invalid = + |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; - {} + assert_eq!( + sanitize_backed_candidates::( + backed_candidates.clone(), + &>::allowed_relay_parents(), + has_concluded_invalid, + scheduled, + core_index_enabled + ), + SanitizedBackedCandidates { + backed_candidates_with_core: expected_all_backed_candidates_with_core, + votes_from_disabled_were_dropped: false, + dropped_unscheduled_candidates: true + } + ); }); } // nothing is scheduled, so no paraids match, thus all backed candidates are skipped - #[test] - fn nothing_scheduled() { + #[rstest] + #[case(false, false)] + #[case(true, true)] + #[case(false, true)] + #[case(true, false)] + fn nothing_scheduled( + #[case] core_index_enabled: bool, + #[case] multiple_cores_per_para: bool, + ) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { backed_candidates, scheduled_paras: _ } = get_test_data(); - let scheduled = &BTreeMap::new(); + let TestData { backed_candidates, .. } = if multiple_cores_per_para { + get_test_data_multiple_cores_per_para(core_index_enabled) + } else { + get_test_data(core_index_enabled) + }; + let scheduled = BTreeMap::new(); let has_concluded_invalid = |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; let SanitizedBackedCandidates { - backed_candidates: sanitized_backed_candidates, + backed_candidates_with_core: sanitized_backed_candidates, votes_from_disabled_were_dropped, + dropped_unscheduled_candidates, } = sanitize_backed_candidates::( backed_candidates.clone(), &>::allowed_relay_parents(), has_concluded_invalid, - &scheduled, + scheduled, + core_index_enabled, ); assert!(sanitized_backed_candidates.is_empty()); assert!(!votes_from_disabled_were_dropped); + assert!(dropped_unscheduled_candidates); }); } // candidates that have concluded as invalid are filtered out - #[test] - fn invalid_are_filtered_out() { + #[rstest] + #[case(false)] + #[case(true)] + fn invalid_are_filtered_out(#[case] core_index_enabled: bool) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { backed_candidates, scheduled_paras: scheduled } = get_test_data(); + let TestData { backed_candidates, scheduled_paras: scheduled, .. } = + get_test_data(core_index_enabled); // mark every second one as concluded invalid let set = { @@ -1440,45 +1878,55 @@ mod sanitizers { let has_concluded_invalid = |_idx: usize, candidate: &BackedCandidate| set.contains(&candidate.hash()); let SanitizedBackedCandidates { - backed_candidates: sanitized_backed_candidates, + backed_candidates_with_core: sanitized_backed_candidates, votes_from_disabled_were_dropped, + dropped_unscheduled_candidates, } = sanitize_backed_candidates::( backed_candidates.clone(), &>::allowed_relay_parents(), has_concluded_invalid, - &scheduled, + scheduled, + core_index_enabled, ); assert_eq!(sanitized_backed_candidates.len(), backed_candidates.len() / 2); assert!(!votes_from_disabled_were_dropped); + assert!(!dropped_unscheduled_candidates); }); } - #[test] - fn disabled_non_signing_validator_doesnt_get_filtered() { + #[rstest] + #[case(false)] + #[case(true)] + fn disabled_non_signing_validator_doesnt_get_filtered(#[case] core_index_enabled: bool) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { mut backed_candidates, scheduled_paras } = get_test_data(); + let TestData { mut all_backed_candidates_with_core, .. } = + get_test_data(core_index_enabled); // Disable Eve set_disabled_validators(vec![4]); - let before = backed_candidates.clone(); + let before = all_backed_candidates_with_core.clone(); // Eve is disabled but no backing statement is signed by it so nothing should be // filtered assert!(!filter_backed_statements_from_disabled_validators::( - &mut backed_candidates, + &mut all_backed_candidates_with_core, &>::allowed_relay_parents(), - &scheduled_paras + core_index_enabled )); - assert_eq!(backed_candidates, before); + assert_eq!(all_backed_candidates_with_core, before); }); } - - #[test] - fn drop_statements_from_disabled_without_dropping_candidate() { + #[rstest] + #[case(false)] + #[case(true)] + fn drop_statements_from_disabled_without_dropping_candidate( + #[case] core_index_enabled: bool, + ) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { mut backed_candidates, scheduled_paras } = get_test_data(); + let TestData { mut all_backed_candidates_with_core, .. } = + get_test_data(core_index_enabled); // Disable Alice set_disabled_validators(vec![0]); @@ -1491,61 +1939,83 @@ mod sanitizers { configuration::Pallet::::force_set_active_config(hc); // Verify the initial state is as expected - assert_eq!(backed_candidates.get(0).unwrap().validity_votes.len(), 2); assert_eq!( - backed_candidates.get(0).unwrap().validator_indices.get(0).unwrap(), - true + all_backed_candidates_with_core.get(0).unwrap().0.validity_votes().len(), + 2 ); - assert_eq!( - backed_candidates.get(0).unwrap().validator_indices.get(1).unwrap(), - true - ); - let untouched = backed_candidates.get(1).unwrap().clone(); + let (validator_indices, maybe_core_index) = all_backed_candidates_with_core + .get(0) + .unwrap() + .0 + .validator_indices_and_core_index(core_index_enabled); + if core_index_enabled { + assert!(maybe_core_index.is_some()); + } else { + assert!(maybe_core_index.is_none()); + } + + assert_eq!(validator_indices.get(0).unwrap(), true); + assert_eq!(validator_indices.get(1).unwrap(), true); + let untouched = all_backed_candidates_with_core.get(1).unwrap().0.clone(); assert!(filter_backed_statements_from_disabled_validators::( - &mut backed_candidates, + &mut all_backed_candidates_with_core, &>::allowed_relay_parents(), - &scheduled_paras + core_index_enabled )); + let (validator_indices, maybe_core_index) = all_backed_candidates_with_core + .get(0) + .unwrap() + .0 + .validator_indices_and_core_index(core_index_enabled); + if core_index_enabled { + assert!(maybe_core_index.is_some()); + } else { + assert!(maybe_core_index.is_none()); + } + // there should still be two backed candidates - assert_eq!(backed_candidates.len(), 2); + assert_eq!(all_backed_candidates_with_core.len(), 2); // but the first one should have only one validity vote - assert_eq!(backed_candidates.get(0).unwrap().validity_votes.len(), 1); - // Validator 0 vote should be dropped, validator 1 - retained assert_eq!( - backed_candidates.get(0).unwrap().validator_indices.get(0).unwrap(), - false - ); - assert_eq!( - backed_candidates.get(0).unwrap().validator_indices.get(1).unwrap(), - true + all_backed_candidates_with_core.get(0).unwrap().0.validity_votes().len(), + 1 ); + // Validator 0 vote should be dropped, validator 1 - retained + assert_eq!(validator_indices.get(0).unwrap(), false); + assert_eq!(validator_indices.get(1).unwrap(), true); // the second candidate shouldn't be modified - assert_eq!(*backed_candidates.get(1).unwrap(), untouched); + assert_eq!(all_backed_candidates_with_core.get(1).unwrap().0, untouched); }); } - #[test] - fn drop_candidate_if_all_statements_are_from_disabled() { + #[rstest] + #[case(false)] + #[case(true)] + fn drop_candidate_if_all_statements_are_from_disabled(#[case] core_index_enabled: bool) { new_test_ext(MockGenesisConfig::default()).execute_with(|| { - let TestData { mut backed_candidates, scheduled_paras } = get_test_data(); + let TestData { mut all_backed_candidates_with_core, .. } = + get_test_data(core_index_enabled); // Disable Alice and Bob set_disabled_validators(vec![0, 1]); // Verify the initial state is as expected - assert_eq!(backed_candidates.get(0).unwrap().validity_votes.len(), 2); - let untouched = backed_candidates.get(1).unwrap().clone(); + assert_eq!( + all_backed_candidates_with_core.get(0).unwrap().0.validity_votes().len(), + 2 + ); + let untouched = all_backed_candidates_with_core.get(1).unwrap().0.clone(); assert!(filter_backed_statements_from_disabled_validators::( - &mut backed_candidates, + &mut all_backed_candidates_with_core, &>::allowed_relay_parents(), - &scheduled_paras + core_index_enabled )); - assert_eq!(backed_candidates.len(), 1); - assert_eq!(*backed_candidates.get(0).unwrap(), untouched); + assert_eq!(all_backed_candidates_with_core.len(), 1); + assert_eq!(all_backed_candidates_with_core.get(0).unwrap().0, untouched); }); } } diff --git a/polkadot/runtime/parachains/src/paras_inherent/weights.rs b/polkadot/runtime/parachains/src/paras_inherent/weights.rs index 05cc53fae04652c188d62b57fa5281aa6bb88e22..0f4e5be572a66d16c1476ada7671c495a27bbc83 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/weights.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/weights.rs @@ -149,11 +149,11 @@ pub fn backed_candidate_weight( candidate: &BackedCandidate, ) -> Weight { set_proof_size_to_tx_size( - if candidate.candidate.commitments.new_validation_code.is_some() { + if candidate.candidate().commitments.new_validation_code.is_some() { <::WeightInfo as WeightInfo>::enter_backed_candidate_code_upgrade() } else { <::WeightInfo as WeightInfo>::enter_backed_candidates_variable( - candidate.validity_votes.len() as u32, + candidate.validity_votes().len() as u32, ) }, candidate, diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs index b3a060e1cb8a05439de7f48fdd6ff1a84c554496..1bbd4dfb716f1f541b40bca3e3a434b21c5a7a81 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v7.rs @@ -45,6 +45,8 @@ pub fn validators() -> Vec { /// Implementation for the `validator_groups` function of the runtime API. pub fn validator_groups( ) -> (Vec>, GroupRotationInfo>) { + // This formula needs to be the same as the one we use + // when populating group_responsible in `availability_cores` let now = >::block_number() + One::one(); let groups = >::validator_groups(); @@ -95,6 +97,11 @@ pub fn availability_cores() -> Vec>::next_up_on_available(CoreIndex( i as u32, @@ -106,7 +113,7 @@ pub fn availability_cores() -> Vec), } - /// Conveninece type alias for `CoreOccupied`. + /// Convenience type alias for `CoreOccupied`. pub type CoreOccupiedType = CoreOccupied>; impl CoreOccupied { @@ -145,9 +145,7 @@ pub mod pallet { pub(crate) type SessionStartBlock = StorageValue<_, BlockNumberFor, ValueQuery>; /// One entry for each availability core. The `VecDeque` represents the assignments to be - /// scheduled on that core. `None` is used to signal to not schedule the next para of the core - /// as there is one currently being scheduled. Not using `None` here would overwrite the - /// `CoreState` in the runtime API. The value contained here will not be valid after the end of + /// scheduled on that core. The value contained here will not be valid after the end of /// a block. Runtime APIs should be used to determine scheduled cores/ for the upcoming block. #[pallet::storage] #[pallet::getter(fn claimqueue)] diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 11be7a9ffc4396e7deeabf7527bdf72e6c0bf92e..3dc59cc172817409bd607f5b68e52163a7b9afdb 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -13,9 +13,9 @@ workspace = true [dependencies] parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } -serde = { version = "1.0.195", default-features = false } -serde_derive = { version = "1.0.117", optional = true } +log = { workspace = true } +serde = { workspace = true } +serde_derive = { optional = true, workspace = true } static_assertions = "1.1.0" smallvec = "1.8.0" @@ -111,7 +111,7 @@ keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyrin remote-externalities = { package = "frame-remote-externalities", path = "../../../substrate/utils/frame/remote-externalities" } sp-trie = { path = "../../../substrate/primitives/trie" } separator = "0.4.1" -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } tokio = { version = "1.24.2", features = ["macros"] } diff --git a/polkadot/runtime/rococo/src/governance/fellowship.rs b/polkadot/runtime/rococo/src/governance/fellowship.rs index 9ac47e008a2719c6f637b2c9a58b3a8ae75b4c65..a589b768afde2c0757e74a6535509228f1613a82 100644 --- a/polkadot/runtime/rococo/src/governance/fellowship.rs +++ b/polkadot/runtime/rococo/src/governance/fellowship.rs @@ -17,7 +17,7 @@ //! Elements of governance concerning the Rococo Fellowship. use frame_support::traits::{MapSuccess, TryMapSuccess}; -use sp_runtime::traits::{CheckedReduceBy, ConstU16, Replace}; +use sp_runtime::traits::{CheckedReduceBy, ConstU16, Replace, ReplaceWithDefault}; use super::*; use crate::{CENTS, DAYS}; @@ -315,6 +315,11 @@ pub type FellowshipCollectiveInstance = pallet_ranked_collective::Instance1; impl pallet_ranked_collective::Config for Runtime { type WeightInfo = weights::pallet_ranked_collective::WeightInfo; type RuntimeEvent = RuntimeEvent; + // Adding is by any of: + // - Root. + // - the FellowshipAdmin origin. + // - a Fellowship origin. + type AddOrigin = MapSuccess>; // Promotion is by any of: // - Root can demote arbitrarily. // - the FellowshipAdmin origin (i.e. token holder referendum); @@ -326,6 +331,11 @@ impl pallet_ranked_collective::Config for Runtime TryMapSuccess>>, >, >; + // Removing is by any of: + // - Root can remove arbitrarily. + // - the FellowshipAdmin origin (i.e. token holder referendum); + // - a vote by the rank two above the current rank. + type RemoveOrigin = Self::DemoteOrigin; // Demotion is by any of: // - Root can demote arbitrarily. // - the FellowshipAdmin origin (i.e. token holder referendum); diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 79c848221fbfdf1490547179f46dc8b3d69a152d..bbc79a13d37f4987eb7bfc4f1619b8141255467c 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -46,9 +46,8 @@ use sp_std::{cmp::Ordering, collections::btree_map::BTreeMap, prelude::*}; use runtime_parachains::{ assigner_coretime as parachains_assigner_coretime, - assigner_on_demand as parachains_assigner_on_demand, - assigner_parachains as parachains_assigner_parachains, - configuration as parachains_configuration, coretime, disputes as parachains_disputes, + assigner_on_demand as parachains_assigner_on_demand, configuration as parachains_configuration, + coretime, disputes as parachains_disputes, disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, inclusion::{AggregateMessageOrigin, UmpQueueId}, @@ -150,7 +149,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("rococo"), impl_name: create_runtime_str!("parity-rococo-v2.0"), authoring_version: 0, - spec_version: 1_006_002, + spec_version: 1_007_000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 24, @@ -1038,8 +1037,6 @@ impl parachains_assigner_on_demand::Config for Runtime { type WeightInfo = weights::runtime_parachains_assigner_on_demand::WeightInfo; } -impl parachains_assigner_parachains::Config for Runtime {} - impl parachains_assigner_coretime::Config for Runtime {} impl parachains_initializer::Config for Runtime { @@ -1401,7 +1398,6 @@ construct_runtime! { ParasSlashing: parachains_slashing = 63, MessageQueue: pallet_message_queue = 64, OnDemandAssignmentProvider: parachains_assigner_on_demand = 66, - ParachainsAssignmentProvider: parachains_assigner_parachains = 67, CoretimeAssignmentProvider: parachains_assigner_coretime = 68, // Parachain Onboarding Pallets. Start indices at 70 to leave room. @@ -1662,6 +1658,9 @@ pub mod migrations { parachains_configuration::migration::v11::MigrateToV11, // This needs to come after the `parachains_configuration` above as we are reading the configuration. coretime::migration::MigrateToCoretime, + + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, ); } @@ -1821,6 +1820,12 @@ sp_api::impl_runtime_apis! { impl offchain_primitives::OffchainWorkerApi for Runtime { fn offchain_worker(header: &::Header) { + use sp_runtime::{traits::Header, DigestItem}; + + if header.digest().logs().iter().any(|di| di == &DigestItem::RuntimeEnvironmentUpdated) { + pallet_im_online::migration::clear_offchain_storage(Session::validators().len() as u32); + } + Executive::offchain_worker(header) } } @@ -2267,12 +2272,30 @@ sp_api::impl_runtime_apis! { TokenLocation::get(), ExistentialDeposit::get() ).into()); - pub ToParachain: ParaId = rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub AssetHubParaId: ParaId = rococo_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub const RandomParaId: ParaId = ParaId::new(43211234); } impl frame_system_benchmarking::Config for Runtime {} impl frame_benchmarking::baseline::Config for Runtime {} impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + runtime_common::xcm_sender::ToParachainDeliveryHelper< + XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + AssetHubParaId, + (), + >, + runtime_common::xcm_sender::ToParachainDeliveryHelper< + XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + RandomParaId, + (), + > + ); + fn reachable_dest() -> Option { Some(crate::xcm_config::AssetHub::get()) } @@ -2281,7 +2304,7 @@ sp_api::impl_runtime_apis! { // Relay/native token can be teleported to/from AH. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Here.into()) }, crate::xcm_config::AssetHub::get(), @@ -2292,10 +2315,10 @@ sp_api::impl_runtime_apis! { // Relay can reserve transfer native token to some random parachain. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Here.into()) }, - Parachain(43211234).into(), + Parachain(RandomParaId::get().into()).into(), )) } @@ -2320,7 +2343,7 @@ sp_api::impl_runtime_apis! { XcmConfig, ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, - ToParachain, + AssetHubParaId, (), >; fn valid_destination() -> Result { diff --git a/polkadot/runtime/rococo/src/weights/mod.rs b/polkadot/runtime/rococo/src/weights/mod.rs index 0ba21ccf322d3fcdc63ac83261f5d4c3338de4e2..7328dca9393693e335b49dc317d6754f9fd6e840 100644 --- a/polkadot/runtime/rococo/src/weights/mod.rs +++ b/polkadot/runtime/rococo/src/weights/mod.rs @@ -23,7 +23,6 @@ pub mod pallet_bounties; pub mod pallet_child_bounties; pub mod pallet_conviction_voting; pub mod pallet_identity; -pub mod pallet_im_online; pub mod pallet_indices; pub mod pallet_message_queue; pub mod pallet_multisig; diff --git a/polkadot/runtime/rococo/src/weights/pallet_im_online.rs b/polkadot/runtime/rococo/src/weights/pallet_im_online.rs deleted file mode 100644 index b866426de52a3abc7608f96bb192a1ae4fbb1d90..0000000000000000000000000000000000000000 --- a/polkadot/runtime/rococo/src/weights/pallet_im_online.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 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 . - -//! Autogenerated weights for `pallet_im_online` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `build-host`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=rococo-dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_im_online -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -/// Weight functions for `pallet_im_online`. -pub struct WeightInfo(PhantomData); -impl pallet_im_online::WeightInfo for WeightInfo { - /// Storage: Session Validators (r:1 w:0) - /// Proof Skipped: Session Validators (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ImOnline Keys (r:1 w:0) - /// Proof: ImOnline Keys (max_values: Some(1), max_size: Some(320002), added: 320497, mode: MaxEncodedLen) - /// Storage: unknown `0x39e295d143ed41353167609a3d816584` (r:1 w:0) - /// Proof Skipped: unknown `0x39e295d143ed41353167609a3d816584` (r:1 w:0) - /// Storage: ImOnline ReceivedHeartbeats (r:1 w:1) - /// Proof: ImOnline ReceivedHeartbeats (max_values: None, max_size: Some(1028), added: 3503, mode: MaxEncodedLen) - /// Storage: ImOnline AuthoredBlocks (r:1 w:0) - /// Proof: ImOnline AuthoredBlocks (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) - /// The range of component `k` is `[1, 1000]`. - fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `394 + k * (32 ±0)` - // Estimated: `321487 + k * (1761 ±0)` - // Minimum execution time: 132_910_000 picoseconds. - Weight::from_parts(149_854_501, 0) - .saturating_add(Weight::from_parts(0, 321487)) - // Standard Error: 3_317 - .saturating_add(Weight::from_parts(61_141, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 1761).saturating_mul(k.into())) - } -} diff --git a/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs b/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs index e4732a2d17dca5180a8e07d5cddd19acd55f1b76..0f36dbd384df87d5a90c2a11392923e7ae8633aa 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_scheduler.rs @@ -17,24 +17,25 @@ //! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm5`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=rococo-dev // --steps=50 // --repeat=20 -// --pallet=pallet_scheduler // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/rococo/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=rococo-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,30 +48,30 @@ use core::marker::PhantomData; /// Weight functions for `pallet_scheduler`. pub struct WeightInfo(PhantomData); impl pallet_scheduler::WeightInfo for WeightInfo { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: - // Measured: `69` + // Measured: `68` // Estimated: `1489` - // Minimum execution time: 4_741_000 picoseconds. - Weight::from_parts(4_939_000, 0) + // Minimum execution time: 2_869_000 picoseconds. + Weight::from_parts(3_109_000, 0) .saturating_add(Weight::from_parts(0, 1489)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 50]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `116 + s * (177 ±0)` + // Measured: `115 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 4_504_000 picoseconds. - Weight::from_parts(7_569_333, 0) + // Minimum execution time: 3_326_000 picoseconds. + Weight::from_parts(5_818_563, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_818 - .saturating_add(Weight::from_parts(771_180, 0).saturating_mul(s.into())) + // Standard Error: 1_261 + .saturating_add(Weight::from_parts(336_446, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -78,36 +79,38 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_709_000 picoseconds. - Weight::from_parts(5_929_000, 0) + // Minimum execution time: 3_007_000 picoseconds. + Weight::from_parts(3_197_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `251 + s * (1 ±0)` // Estimated: `3716 + s * (1 ±0)` - // Minimum execution time: 20_710_000 picoseconds. - Weight::from_parts(20_918_000, 0) + // Minimum execution time: 16_590_000 picoseconds. + Weight::from_parts(16_869_000, 0) .saturating_add(Weight::from_parts(0, 3716)) // Standard Error: 9 - .saturating_add(Weight::from_parts(1_257, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(Weight::from_parts(1_308, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_262_000 picoseconds. - Weight::from_parts(7_412_000, 0) + // Minimum execution time: 4_320_000 picoseconds. + Weight::from_parts(4_594_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -115,90 +118,173 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_774_000 picoseconds. - Weight::from_parts(5_887_000, 0) + // Minimum execution time: 2_956_000 picoseconds. + Weight::from_parts(3_216_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_777_000 picoseconds. - Weight::from_parts(2_865_000, 0) + // Minimum execution time: 1_824_000 picoseconds. + Weight::from_parts(1_929_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_739_000 picoseconds. - Weight::from_parts(2_827_000, 0) + // Minimum execution time: 1_749_000 picoseconds. + Weight::from_parts(1_916_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `116 + s * (177 ±0)` + // Measured: `115 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 14_788_000 picoseconds. - Weight::from_parts(17_705_748, 0) + // Minimum execution time: 9_086_000 picoseconds. + Weight::from_parts(11_733_696, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_703 - .saturating_add(Weight::from_parts(760_991, 0).saturating_mul(s.into())) + // Standard Error: 1_362 + .saturating_add(Weight::from_parts(375_266, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `116 + s * (177 ±0)` + // Measured: `115 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 18_716_000 picoseconds. - Weight::from_parts(18_220_022, 0) + // Minimum execution time: 12_716_000 picoseconds. + Weight::from_parts(12_529_180, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_508 - .saturating_add(Weight::from_parts(1_357_835, 0).saturating_mul(s.into())) + // Standard Error: 867 + .saturating_add(Weight::from_parts(548_188, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `293 + s * (185 ±0)` + // Measured: `292 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 17_719_000 picoseconds. - Weight::from_parts(21_657_806, 0) + // Minimum execution time: 12_053_000 picoseconds. + Weight::from_parts(15_358_056, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 2_645 - .saturating_add(Weight::from_parts(794_184, 0).saturating_mul(s.into())) + // Standard Error: 3_176 + .saturating_add(Weight::from_parts(421_589, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `319 + s * (185 ±0)` + // Measured: `318 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 20_225_000 picoseconds. - Weight::from_parts(20_494_405, 0) + // Minimum execution time: 14_803_000 picoseconds. + Weight::from_parts(15_805_714, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 1_890 - .saturating_add(Weight::from_parts(1_379_025, 0).saturating_mul(s.into())) + // Standard Error: 2_597 + .saturating_add(Weight::from_parts(611_053, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `196` + // Estimated: `42428` + // Minimum execution time: 13_156_000 picoseconds. + Weight::from_parts(13_801_287, 0) + .saturating_add(Weight::from_parts(0, 42428)) + // Standard Error: 568 + .saturating_add(Weight::from_parts(35_441, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `115 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 7_912_000 picoseconds. + Weight::from_parts(8_081_460, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `324 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_673_000 picoseconds. + Weight::from_parts(12_212_185, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `115 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 7_912_000 picoseconds. + Weight::from_parts(8_081_460, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `324 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_673_000 picoseconds. + Weight::from_parts(12_212_185, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index 5fddd749dad3859d13cafa93818aef6d03fd9f03..af8981ddcc146573bdf2179c9baae856269e4b93 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -36,17 +36,15 @@ use runtime_common::{ }; use sp_core::ConstU32; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, DescribeBodyTerminal, DescribeFamily, FixedWeightBounds, - FrameTransactionalProcessor, HashedDescription, IsChildSystemParachain, IsConcrete, - MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, - XcmFeeToAccount, + FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsChildSystemParachain, + IsConcrete, MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, + SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, + UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; @@ -73,8 +71,7 @@ pub type LocationConverter = ( /// point of view of XCM-only concepts like `Location` and `Asset`. /// /// Ours is only aware of the Balances pallet, which is mapped to `RocLocation`. -#[allow(deprecated)] -pub type LocalAssetTransactor = XcmCurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index 9778ff82439494767b83d5acb6f7f9f57ac89619..9753a4093045b52c8c709cfcea4a19a15dc510d6 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -13,11 +13,11 @@ workspace = true [dependencies] bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false } -serde_derive = { version = "1.0.117", optional = true } +serde = { workspace = true } +serde_derive = { optional = true, workspace = true } smallvec = "1.8.0" authority-discovery-primitives = { package = "sp-authority-discovery", path = "../../../substrate/primitives/authority-discovery", default-features = false } @@ -74,7 +74,7 @@ hex-literal = "0.4.1" tiny-keccak = { version = "2.0.2", features = ["keccak"] } keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyring" } sp-trie = { path = "../../../substrate/primitives/trie" } -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { path = "../../../substrate/utils/wasm-builder" } diff --git a/polkadot/runtime/test-runtime/src/xcm_config.rs b/polkadot/runtime/test-runtime/src/xcm_config.rs index a81d35f4d788f1628cf20cf5cbd0e366828101ff..a48bca17e9ff988da3d0ca4601003f3f6d96a71d 100644 --- a/polkadot/runtime/test-runtime/src/xcm_config.rs +++ b/polkadot/runtime/test-runtime/src/xcm_config.rs @@ -16,14 +16,16 @@ use frame_support::{ parameter_types, - traits::{Everything, Nothing}, + traits::{Everything, Get, Nothing}, weights::Weight, }; use frame_system::EnsureRoot; +use polkadot_runtime_parachains::FeeTracker; +use runtime_common::xcm_sender::{ChildParachainRouter, PriceForMessageDelivery}; use xcm::latest::prelude::*; use xcm_builder::{ AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, - SignedAccountId32AsNative, SignedToAccountId32, + SignedAccountId32AsNative, SignedToAccountId32, WithUniqueTopic, }; use xcm_executor::{ traits::{TransactAsset, WeightTrader}, @@ -36,6 +38,8 @@ parameter_types! { pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 16; pub const UniversalLocation: xcm::latest::InteriorLocation = xcm::latest::Junctions::Here; + pub TokenLocation: Location = Here.into_location(); + pub FeeAssetId: AssetId = AssetId(TokenLocation::get()); } /// Type to convert an `Origin` type value into a `Location` value which represents an interior @@ -45,17 +49,43 @@ pub type LocalOriginToLocation = ( SignedToAccountId32, ); -pub struct DoNothingRouter; -impl SendXcm for DoNothingRouter { - type Ticket = (); - fn validate(_dest: &mut Option, _msg: &mut Option>) -> SendResult<()> { - Ok(((), Assets::new())) - } - fn deliver(_: ()) -> Result { - Ok([0; 32]) +/// Implementation of [`PriceForMessageDelivery`], returning a different price +/// based on whether a message contains a reanchored asset or not. +/// This implementation ensures that messages with non-reanchored assets return higher +/// prices than messages with reanchored assets. +/// Useful for `deposit_reserve_asset_works_for_any_xcm_sender` integration test. +pub struct TestDeliveryPrice(sp_std::marker::PhantomData<(A, F)>); +impl, F: FeeTracker> PriceForMessageDelivery for TestDeliveryPrice { + type Id = F::Id; + + fn price_for_delivery(_: Self::Id, msg: &Xcm<()>) -> Assets { + let base_fee: super::Balance = 1_000_000; + + let parents = msg.iter().find_map(|xcm| match xcm { + ReserveAssetDeposited(assets) => { + let AssetId(location) = &assets.inner().first().unwrap().id; + Some(location.parents) + }, + _ => None, + }); + + // If no asset is found, price defaults to `base_fee`. + let amount = base_fee + .saturating_add(base_fee.saturating_mul(parents.unwrap_or(0) as super::Balance)); + + (A::get(), amount).into() } } +pub type PriceForChildParachainDelivery = TestDeliveryPrice; + +/// The XCM router. When we want to send an XCM message, we use this type. It amalgamates all of our +/// individual routers. +pub type XcmRouter = WithUniqueTopic< + // Only one router so far - use DMP to communicate with child parachains. + ChildParachainRouter, +>; + pub type Barrier = AllowUnpaidExecutionFrom; pub struct DummyAssetTransactor; @@ -99,7 +129,7 @@ type OriginConverter = ( pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = super::RuntimeCall; - type XcmSender = DoNothingRouter; + type XcmSender = XcmRouter; type AssetTransactor = DummyAssetTransactor; type OriginConverter = OriginConverter; type IsReserve = (); @@ -133,7 +163,7 @@ impl pallet_xcm::Config for crate::Runtime { type UniversalLocation = UniversalLocation; type SendXcmOrigin = EnsureXcmOrigin; type Weigher = FixedWeightBounds; - type XcmRouter = DoNothingRouter; + type XcmRouter = XcmRouter; type XcmExecuteFilter = Everything; type XcmExecutor = xcm_executor::XcmExecutor; type XcmTeleportFilter = Everything; diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 0ea2436b6811eb74a4e402eb49c38b176592a869..4180828bcfb14d497d9da06bc74da91fa53f8d5f 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -14,10 +14,10 @@ workspace = true bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rustc-hex = { version = "2.1.0", default-features = false } -serde = { version = "1.0.195", default-features = false } -serde_derive = { version = "1.0.117", optional = true } +serde = { workspace = true } +serde_derive = { optional = true, workspace = true } smallvec = "1.8.0" authority-discovery-primitives = { package = "sp-authority-discovery", path = "../../../substrate/primitives/authority-discovery", default-features = false } @@ -119,7 +119,7 @@ xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", hex-literal = "0.4.1" tiny-keccak = { version = "2.0.2", features = ["keccak"] } keyring = { package = "sp-keyring", path = "../../../substrate/primitives/keyring" } -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } remote-externalities = { package = "frame-remote-externalities", path = "../../../substrate/utils/frame/remote-externalities" } tokio = { version = "1.24.2", features = ["macros"] } sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false } diff --git a/polkadot/runtime/westend/constants/src/lib.rs b/polkadot/runtime/westend/constants/src/lib.rs index 848cccd559dc3e50cbd2a50c4df6a643910fe35c..3bc27177d7a3f5390abf9ddd0e2287f1ce8bc783 100644 --- a/polkadot/runtime/westend/constants/src/lib.rs +++ b/polkadot/runtime/westend/constants/src/lib.rs @@ -107,6 +107,8 @@ pub mod system_parachain { pub const COLLECTIVES_ID: u32 = 1001; /// BridgeHub parachain ID. pub const BRIDGE_HUB_ID: u32 = 1002; + /// Encointer parachain ID. + pub const ENCOINTER_ID: u32 = 1003; /// People Chain parachain ID. pub const PEOPLE_ID: u32 = 1004; /// Brokerage parachain ID. diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 2eb592d1328dc6183509163e1599b9e96975c3d3..7ea4a3f0a80fce961b3edd4952dda861e9850102 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -61,12 +61,15 @@ use runtime_common::{ impls::{ LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedLocationConverter, }, - paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, BalanceToU256, BlockHashCount, - BlockLength, CurrencyToVote, SlowAdjustingFeeUpdate, U256ToBalance, + paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, + traits::Leaser, + BalanceToU256, BlockHashCount, BlockLength, CurrencyToVote, SlowAdjustingFeeUpdate, + U256ToBalance, }; use runtime_parachains::{ - assigner_parachains as parachains_assigner_parachains, - configuration as parachains_configuration, disputes as parachains_disputes, + assigner_coretime as parachains_assigner_coretime, + assigner_on_demand as parachains_assigner_on_demand, configuration as parachains_configuration, + coretime, disputes as parachains_disputes, disputes::slashing as parachains_slashing, dmp as parachains_dmp, hrmp as parachains_hrmp, inclusion as parachains_inclusion, inclusion::{AggregateMessageOrigin, UmpQueueId}, @@ -147,7 +150,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("westend"), impl_name: create_runtime_str!("parity-westend"), authoring_version: 2, - spec_version: 1_006_001, + spec_version: 1_007_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 24, @@ -1142,7 +1145,7 @@ impl parachains_paras::Config for Runtime { type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; type OnNewHead = (); - type AssignCoretime = (); + type AssignCoretime = CoretimeAssignmentProvider; } parameter_types! { @@ -1211,20 +1214,40 @@ impl parachains_paras_inherent::Config for Runtime { impl parachains_scheduler::Config for Runtime { // If you change this, make sure the `Assignment` type of the new provider is binary compatible, // otherwise provide a migration. - type AssignmentProvider = ParachainsAssignmentProvider; + type AssignmentProvider = CoretimeAssignmentProvider; } parameter_types! { pub const BrokerId: u32 = BROKER_ID; } -impl parachains_assigner_parachains::Config for Runtime {} +impl coretime::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BrokerId = BrokerId; + type WeightInfo = weights::runtime_parachains_coretime::WeightInfo; + type SendXcm = crate::xcm_config::XcmRouter; +} + +parameter_types! { + pub const OnDemandTrafficDefaultValue: FixedU128 = FixedU128::from_u32(1); +} + +impl parachains_assigner_on_demand::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type TrafficDefaultValue = OnDemandTrafficDefaultValue; + type WeightInfo = weights::runtime_parachains_assigner_on_demand::WeightInfo; +} + +impl parachains_assigner_coretime::Config for Runtime {} impl parachains_initializer::Config for Runtime { type Randomness = pallet_babe::RandomnessFromOneEpochAgo; type ForceOrigin = EnsureRoot; type WeightInfo = weights::runtime_parachains_initializer::WeightInfo; - type CoretimeOnNewSession = (); + type CoretimeOnNewSession = Coretime; } impl paras_sudo_wrapper::Config for Runtime {} @@ -1478,7 +1501,8 @@ construct_runtime! { ParaSessionInfo: parachains_session_info = 52, ParasDisputes: parachains_disputes = 53, ParasSlashing: parachains_slashing = 54, - ParachainsAssignmentProvider: parachains_assigner_parachains = 55, + OnDemandAssignmentProvider: parachains_assigner_on_demand = 56, + CoretimeAssignmentProvider: parachains_assigner_coretime = 57, // Parachain Onboarding Pallets. Start indices at 60 to leave room. Registrar: paras_registrar = 60, @@ -1487,6 +1511,7 @@ construct_runtime! { Auctions: auctions = 63, Crowdloan: crowdloan = 64, AssignedSlots: assigned_slots = 65, + Coretime: coretime = 66, // Pallet for sending XCM. XcmPallet: pallet_xcm = 99, @@ -1554,6 +1579,24 @@ pub mod migrations { #[cfg(feature = "try-runtime")] use sp_core::crypto::ByteArray; + pub struct GetLegacyLeaseImpl; + impl coretime::migration::GetLegacyLease for GetLegacyLeaseImpl { + fn get_parachain_lease_in_blocks(para: ParaId) -> Option { + let now = frame_system::Pallet::::block_number(); + let lease = slots::Pallet::::lease(para); + if lease.is_empty() { + return None + } + // Lease not yet started, ignore: + if lease.iter().any(Option::is_none) { + return None + } + let (index, _) = + as Leaser>::lease_period_index(now)?; + Some(index.saturating_add(lease.len() as u32).saturating_mul(LeasePeriod::get())) + } + } + parameter_types! { pub const ImOnlinePalletName: &'static str = "ImOnline"; } @@ -1647,7 +1690,7 @@ pub mod migrations { pallet_referenda::migration::v1::MigrateV0ToV1, pallet_grandpa::migrations::MigrateV4ToV5, parachains_configuration::migration::v10::MigrateToV10, - pallet_nomination_pools::migration::versioned::V7ToV8, + pallet_nomination_pools::migration::unversioned::TotalValueLockedSync, UpgradeSessionKeys, frame_support::migrations::RemovePallet< ImOnlinePalletName, @@ -1656,6 +1699,14 @@ pub mod migrations { // Migrate Identity pallet for Usernames pallet_identity::migration::versioned::V0ToV1, parachains_configuration::migration::v11::MigrateToV11, + // permanent + pallet_xcm::migration::MigrateToLatestXcmVersion, + // Migrate from legacy lease to coretime. Needs to run after configuration v11 + coretime::migration::MigrateToCoretime< + Runtime, + crate::xcm_config::XcmRouter, + GetLegacyLeaseImpl, + >, ); } @@ -1694,6 +1745,8 @@ mod benches { [runtime_parachains::initializer, Initializer] [runtime_parachains::paras, Paras] [runtime_parachains::paras_inherent, ParaInherent] + [runtime_parachains::assigner_on_demand, OnDemandAssignmentProvider] + [runtime_parachains::coretime, Coretime] // Substrate [pallet_bags_list, VoterList] [pallet_balances, Balances] @@ -1792,6 +1845,12 @@ sp_api::impl_runtime_apis! { impl offchain_primitives::OffchainWorkerApi for Runtime { fn offchain_worker(header: &::Header) { + use sp_runtime::{traits::Header, DigestItem}; + + if header.digest().logs().iter().any(|di| di == &DigestItem::RuntimeEnvironmentUpdated) { + pallet_im_online::migration::clear_offchain_storage(Session::validators().len() as u32); + } + Executive::offchain_worker(header) } } @@ -2285,7 +2344,36 @@ sp_api::impl_runtime_apis! { impl pallet_session_benchmarking::Config for Runtime {} impl pallet_offences_benchmarking::Config for Runtime {} impl pallet_election_provider_support_benchmarking::Config for Runtime {} + + use xcm_config::{AssetHub, TokenLocation}; + + parameter_types! { + pub ExistentialDepositAsset: Option = Some(( + TokenLocation::get(), + ExistentialDeposit::get() + ).into()); + pub AssetHubParaId: ParaId = westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(); + pub const RandomParaId: ParaId = ParaId::new(43211234); + } + impl pallet_xcm::benchmarking::Config for Runtime { + type DeliveryHelper = ( + runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + AssetHubParaId, + (), + >, + runtime_common::xcm_sender::ToParachainDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForChildParachainDelivery, + RandomParaId, + (), + > + ); + fn reachable_dest() -> Option { Some(crate::xcm_config::AssetHub::get()) } @@ -2293,7 +2381,7 @@ sp_api::impl_runtime_apis! { fn teleportable_asset_and_dest() -> Option<(Asset, Location)> { // Relay/native token can be teleported to/from AH. Some(( - Asset { fun: Fungible(EXISTENTIAL_DEPOSIT), id: AssetId(Here.into()) }, + Asset { fun: Fungible(ExistentialDeposit::get()), id: AssetId(Here.into()) }, crate::xcm_config::AssetHub::get(), )) } @@ -2302,10 +2390,10 @@ sp_api::impl_runtime_apis! { // Relay can reserve transfer native token to some random parachain. Some(( Asset { - fun: Fungible(EXISTENTIAL_DEPOSIT), + fun: Fungible(ExistentialDeposit::get()), id: AssetId(Here.into()) }, - crate::Junction::Parachain(43211234).into(), + crate::Junction::Parachain(RandomParaId::get().into()).into(), )) } @@ -2332,15 +2420,6 @@ sp_api::impl_runtime_apis! { AssetId, Fungibility::*, InteriorLocation, Junction, Junctions::*, Asset, Assets, Location, NetworkId, Response, }; - use xcm_config::{AssetHub, TokenLocation}; - - parameter_types! { - pub ExistentialDepositAsset: Option = Some(( - TokenLocation::get(), - ExistentialDeposit::get() - ).into()); - pub ToParachain: ParaId = westend_runtime_constants::system_parachain::ASSET_HUB_ID.into(); - } impl pallet_xcm_benchmarks::Config for Runtime { type XcmConfig = xcm_config::XcmConfig; @@ -2349,7 +2428,7 @@ sp_api::impl_runtime_apis! { xcm_config::XcmConfig, ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, - ToParachain, + AssetHubParaId, (), >; fn valid_destination() -> Result { diff --git a/polkadot/runtime/westend/src/weights/mod.rs b/polkadot/runtime/westend/src/weights/mod.rs index d8a2ae5d2da6fab158898e6cb6548f5f56aa612c..f6a9008d71876726dda2ad240ecdb4588a1f82a3 100644 --- a/polkadot/runtime/westend/src/weights/mod.rs +++ b/polkadot/runtime/westend/src/weights/mod.rs @@ -24,7 +24,6 @@ pub mod pallet_conviction_voting; pub mod pallet_election_provider_multi_phase; pub mod pallet_fast_unstake; pub mod pallet_identity; -pub mod pallet_im_online; pub mod pallet_indices; pub mod pallet_message_queue; pub mod pallet_multisig; diff --git a/polkadot/runtime/westend/src/weights/pallet_im_online.rs b/polkadot/runtime/westend/src/weights/pallet_im_online.rs deleted file mode 100644 index a83cd43804dfdc1ca8827a1988221dd0c6f4ee4d..0000000000000000000000000000000000000000 --- a/polkadot/runtime/westend/src/weights/pallet_im_online.rs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 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 . - -//! Autogenerated weights for `pallet_im_online` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `build-host`, CPU: `AMD EPYC 7601 32-Core Processor` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 - -// Executed Command: -// ./target/production/polkadot -// benchmark -// pallet -// --chain=westend-dev -// --steps=50 -// --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_im_online -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::Weight}; -use core::marker::PhantomData; - -/// Weight functions for `pallet_im_online`. -pub struct WeightInfo(PhantomData); -impl pallet_im_online::WeightInfo for WeightInfo { - /// Storage: Session Validators (r:1 w:0) - /// Proof Skipped: Session Validators (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Session CurrentIndex (r:1 w:0) - /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: ImOnline Keys (r:1 w:0) - /// Proof: ImOnline Keys (max_values: Some(1), max_size: Some(320002), added: 320497, mode: MaxEncodedLen) - /// Storage: ImOnline ReceivedHeartbeats (r:1 w:1) - /// Proof: ImOnline ReceivedHeartbeats (max_values: None, max_size: Some(1028), added: 3503, mode: MaxEncodedLen) - /// Storage: ImOnline AuthoredBlocks (r:1 w:0) - /// Proof: ImOnline AuthoredBlocks (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) - /// The range of component `k` is `[1, 1000]`. - fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `361 + k * (32 ±0)` - // Estimated: `321487 + k * (1761 ±0)` - // Minimum execution time: 122_571_000 picoseconds. - Weight::from_parts(162_954_849, 0) - .saturating_add(Weight::from_parts(0, 321487)) - // Standard Error: 8_676 - .saturating_add(Weight::from_parts(11_122, 0).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(Weight::from_parts(0, 1761).saturating_mul(k.into())) - } -} diff --git a/polkadot/runtime/westend/src/weights/pallet_scheduler.rs b/polkadot/runtime/westend/src/weights/pallet_scheduler.rs index 7291b98093300ef887d422051dcf6be7274c0d12..beef3796dea6e5ab3a47a62238422df652f30797 100644 --- a/polkadot/runtime/westend/src/weights/pallet_scheduler.rs +++ b/polkadot/runtime/westend/src/weights/pallet_scheduler.rs @@ -17,27 +17,25 @@ //! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=westend-dev // --steps=50 // --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=pallet_scheduler // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=westend-dev +// --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,30 +48,30 @@ use core::marker::PhantomData; /// Weight functions for `pallet_scheduler`. pub struct WeightInfo(PhantomData); impl pallet_scheduler::WeightInfo for WeightInfo { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: // Measured: `69` // Estimated: `1489` - // Minimum execution time: 3_991_000 picoseconds. - Weight::from_parts(4_160_000, 0) + // Minimum execution time: 3_220_000 picoseconds. + Weight::from_parts(3_512_000, 0) .saturating_add(Weight::from_parts(0, 1489)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 50]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `116 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 3_647_000 picoseconds. - Weight::from_parts(6_608_270, 0) + // Minimum execution time: 3_565_000 picoseconds. + Weight::from_parts(6_102_216, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 2_516 - .saturating_add(Weight::from_parts(892_866, 0).saturating_mul(s.into())) + // Standard Error: 1_413 + .saturating_add(Weight::from_parts(339_016, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -81,36 +79,38 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_552_000 picoseconds. - Weight::from_parts(5_836_000, 0) + // Minimum execution time: 2_940_000 picoseconds. + Weight::from_parts(3_070_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `217 + s * (1 ±0)` // Estimated: `3682 + s * (1 ±0)` - // Minimum execution time: 20_583_000 picoseconds. - Weight::from_parts(20_771_000, 0) + // Minimum execution time: 16_602_000 picoseconds. + Weight::from_parts(16_834_000, 0) .saturating_add(Weight::from_parts(0, 3682)) - // Standard Error: 11 - .saturating_add(Weight::from_parts(2_250, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Standard Error: 10 + .saturating_add(Weight::from_parts(1_307, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_271_000 picoseconds. - Weight::from_parts(7_447_000, 0) + // Minimum execution time: 4_202_000 picoseconds. + Weight::from_parts(4_383_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -118,90 +118,169 @@ impl pallet_scheduler::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_547_000 picoseconds. - Weight::from_parts(5_776_000, 0) + // Minimum execution time: 2_917_000 picoseconds. + Weight::from_parts(3_043_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_480_000 picoseconds. - Weight::from_parts(2_628_000, 0) + // Minimum execution time: 1_707_000 picoseconds. + Weight::from_parts(1_802_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_479_000 picoseconds. - Weight::from_parts(2_626_000, 0) + // Minimum execution time: 1_671_000 picoseconds. + Weight::from_parts(1_796_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `116 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 13_350_000 picoseconds. - Weight::from_parts(15_289_847, 0) + // Minimum execution time: 9_313_000 picoseconds. + Weight::from_parts(12_146_613, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 5_375 - .saturating_add(Weight::from_parts(974_567, 0).saturating_mul(s.into())) + // Standard Error: 1_381 + .saturating_add(Weight::from_parts(360_418, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `116 + s * (177 ±0)` // Estimated: `42428` - // Minimum execution time: 17_646_000 picoseconds. - Weight::from_parts(15_858_434, 0) + // Minimum execution time: 13_079_000 picoseconds. + Weight::from_parts(12_921_017, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 5_354 - .saturating_add(Weight::from_parts(1_697_642, 0).saturating_mul(s.into())) + // Standard Error: 1_112 + .saturating_add(Weight::from_parts(538_089, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 49]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `293 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 16_419_000 picoseconds. - Weight::from_parts(19_868_760, 0) + // Minimum execution time: 12_458_000 picoseconds. + Weight::from_parts(16_009_539, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 6_915 - .saturating_add(Weight::from_parts(1_010_225, 0).saturating_mul(s.into())) + // Standard Error: 2_260 + .saturating_add(Weight::from_parts(399_245, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(38963), added: 41438, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 50]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `319 + s * (185 ±0)` // Estimated: `42428` - // Minimum execution time: 19_574_000 picoseconds. - Weight::from_parts(18_453_197, 0) + // Minimum execution time: 15_173_000 picoseconds. + Weight::from_parts(15_602_728, 0) .saturating_add(Weight::from_parts(0, 42428)) - // Standard Error: 6_009 - .saturating_add(Weight::from_parts(1_707_130, 0).saturating_mul(s.into())) + // Standard Error: 1_302 + .saturating_add(Weight::from_parts(557_878, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 50]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `197` + // Estimated: `42428` + // Minimum execution time: 13_531_000 picoseconds. + Weight::from_parts(13_985_249, 0) + .saturating_add(Weight::from_parts(0, 42428)) + // Standard Error: 619 + .saturating_add(Weight::from_parts(39_068, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `116 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_440_627, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_876_000 picoseconds. + Weight::from_parts(11_708_172, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `116 + s * (177 ±0)` + // Estimated: `42428` + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_440_627, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(38963), added: 41438, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + s * (185 ±0)` + // Estimated: `42428` + // Minimum execution time: 10_876_000 picoseconds. + Weight::from_parts(11_708_172, 0) + .saturating_add(Weight::from_parts(0, 42428)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index 1ecd44747ef5140b0f83f5226d8368a9231085d0..7a641e36a126bada81cecceadd9c22f57e5b88e9 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -16,10 +16,10 @@ //! Autogenerated weights for `pallet_staking` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-01-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-8idpd4bs-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -62,8 +62,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `894` // Estimated: `4764` - // Minimum execution time: 38_316_000 picoseconds. - Weight::from_parts(40_022_000, 0) + // Minimum execution time: 37_340_000 picoseconds. + Weight::from_parts(38_930_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(4)) @@ -84,8 +84,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1921` // Estimated: `8877` - // Minimum execution time: 81_027_000 picoseconds. - Weight::from_parts(83_964_000, 0) + // Minimum execution time: 80_630_000 picoseconds. + Weight::from_parts(82_196_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) @@ -112,8 +112,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2128` // Estimated: `8877` - // Minimum execution time: 85_585_000 picoseconds. - Weight::from_parts(87_256_000, 0) + // Minimum execution time: 83_523_000 picoseconds. + Weight::from_parts(86_639_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(7)) @@ -133,11 +133,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1075` // Estimated: `4764` - // Minimum execution time: 39_520_000 picoseconds. - Weight::from_parts(41_551_548, 0) + // Minimum execution time: 38_636_000 picoseconds. + Weight::from_parts(40_399_283, 0) .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 1_094 - .saturating_add(Weight::from_parts(50_426, 0).saturating_mul(s.into())) + // Standard Error: 869 + .saturating_add(Weight::from_parts(37_752, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -174,11 +174,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2127 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 82_915_000 picoseconds. - Weight::from_parts(89_597_160, 0) + // Minimum execution time: 81_301_000 picoseconds. + Weight::from_parts(88_609_205, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_146 - .saturating_add(Weight::from_parts(1_228_061, 0).saturating_mul(s.into())) + // Standard Error: 3_388 + .saturating_add(Weight::from_parts(1_253_692, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(11)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -210,8 +210,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1301` // Estimated: `4556` - // Minimum execution time: 48_070_000 picoseconds. - Weight::from_parts(49_226_000, 0) + // Minimum execution time: 47_292_000 picoseconds. + Weight::from_parts(48_566_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(5)) @@ -225,11 +225,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1243 + k * (569 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 29_140_000 picoseconds. - Weight::from_parts(30_225_579, 0) + // Minimum execution time: 28_840_000 picoseconds. + Weight::from_parts(27_510_817, 0) .saturating_add(Weight::from_parts(0, 4556)) - // Standard Error: 5_394 - .saturating_add(Weight::from_parts(6_401_367, 0).saturating_mul(k.into())) + // Standard Error: 6_603 + .saturating_add(Weight::from_parts(6_268_853, 0).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()))) @@ -262,11 +262,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1797 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 59_287_000 picoseconds. - Weight::from_parts(58_285_052, 0) + // Minimum execution time: 57_537_000 picoseconds. + Weight::from_parts(55_854_233, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 14_556 - .saturating_add(Weight::from_parts(3_863_008, 0).saturating_mul(n.into())) + // Standard Error: 14_427 + .saturating_add(Weight::from_parts(3_844_957, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -290,8 +290,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1581` // Estimated: `6248` - // Minimum execution time: 51_035_000 picoseconds. - Weight::from_parts(52_163_000, 0) + // Minimum execution time: 49_997_000 picoseconds. + Weight::from_parts(51_266_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(6)) @@ -306,8 +306,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `865` // Estimated: `4556` - // Minimum execution time: 15_809_000 picoseconds. - Weight::from_parts(16_451_000, 0) + // Minimum execution time: 15_342_000 picoseconds. + Weight::from_parts(15_970_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -322,8 +322,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `932` // Estimated: `4556` - // Minimum execution time: 21_695_000 picoseconds. - Weight::from_parts(22_351_000, 0) + // Minimum execution time: 20_719_000 picoseconds. + Weight::from_parts(21_373_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -336,8 +336,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `865` // Estimated: `4556` - // Minimum execution time: 18_548_000 picoseconds. - Weight::from_parts(19_205_000, 0) + // Minimum execution time: 18_237_000 picoseconds. + Weight::from_parts(18_896_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) @@ -348,8 +348,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_193_000 picoseconds. - Weight::from_parts(2_408_000, 0) + // Minimum execution time: 1_946_000 picoseconds. + Weight::from_parts(2_131_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -359,8 +359,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_475_000 picoseconds. - Weight::from_parts(7_874_000, 0) + // Minimum execution time: 6_840_000 picoseconds. + Weight::from_parts(7_208_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -370,8 +370,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_393_000 picoseconds. - Weight::from_parts(7_643_000, 0) + // Minimum execution time: 6_812_000 picoseconds. + Weight::from_parts(7_254_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -381,8 +381,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_474_000 picoseconds. - Weight::from_parts(7_814_000, 0) + // Minimum execution time: 6_787_000 picoseconds. + Weight::from_parts(7_206_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -393,11 +393,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_358_000 picoseconds. - Weight::from_parts(2_589_423, 0) + // Minimum execution time: 2_045_000 picoseconds. + Weight::from_parts(2_281_841, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 81 - .saturating_add(Weight::from_parts(13_612, 0).saturating_mul(v.into())) + // Standard Error: 70 + .saturating_add(Weight::from_parts(11_592, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Staking::Ledger` (r:751 w:1502) @@ -411,11 +411,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `668 + i * (148 ±0)` // Estimated: `990 + i * (3566 ±0)` - // Minimum execution time: 1_934_000 picoseconds. - Weight::from_parts(2_070_000, 0) + // Minimum execution time: 1_657_000 picoseconds. + Weight::from_parts(1_702_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 19_129 - .saturating_add(Weight::from_parts(13_231_580, 0).saturating_mul(i.into())) + // Standard Error: 20_041 + .saturating_add(Weight::from_parts(13_165_254, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(i.into())) @@ -453,11 +453,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2127 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 80_290_000 picoseconds. - Weight::from_parts(87_901_664, 0) + // Minimum execution time: 78_774_000 picoseconds. + Weight::from_parts(85_770_713, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 2_960 - .saturating_add(Weight::from_parts(1_195_050, 0).saturating_mul(s.into())) + // Standard Error: 2_815 + .saturating_add(Weight::from_parts(1_244_494, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -470,11 +470,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `66639` // Estimated: `70104` - // Minimum execution time: 132_682_000 picoseconds. - Weight::from_parts(932_504_297, 0) + // Minimum execution time: 129_905_000 picoseconds. + Weight::from_parts(932_195_554, 0) .saturating_add(Weight::from_parts(0, 70104)) - // Standard Error: 57_593 - .saturating_add(Weight::from_parts(4_829_705, 0).saturating_mul(s.into())) + // Standard Error: 57_492 + .saturating_add(Weight::from_parts(4_826_754, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -510,12 +510,12 @@ impl pallet_staking::WeightInfo for WeightInfo { fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `8249 + n * (396 ±0)` - // Estimated: `10779 + n * (3774 ±3)` - // Minimum execution time: 129_091_000 picoseconds. - Weight::from_parts(166_186_167, 0) + // Estimated: `10779 + n * (3774 ±0)` + // Minimum execution time: 127_094_000 picoseconds. + Weight::from_parts(160_088_053, 0) .saturating_add(Weight::from_parts(0, 10779)) - // Standard Error: 36_242 - .saturating_add(Weight::from_parts(40_467_481, 0).saturating_mul(n.into())) + // Standard Error: 32_978 + .saturating_add(Weight::from_parts(39_845_710, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4)) @@ -539,11 +539,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1922 + l * (5 ±0)` // Estimated: `8877` - // Minimum execution time: 77_461_000 picoseconds. - Weight::from_parts(80_118_021, 0) + // Minimum execution time: 75_672_000 picoseconds. + Weight::from_parts(78_708_335, 0) .saturating_add(Weight::from_parts(0, 8877)) - // Standard Error: 4_343 - .saturating_add(Weight::from_parts(59_113, 0).saturating_mul(l.into())) + // Standard Error: 3_387 + .saturating_add(Weight::from_parts(37_084, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -578,11 +578,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2127 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 89_366_000 picoseconds. - Weight::from_parts(91_964_557, 0) + // Minimum execution time: 87_991_000 picoseconds. + Weight::from_parts(90_272_005, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 2_799 - .saturating_add(Weight::from_parts(1_206_123, 0).saturating_mul(s.into())) + // Standard Error: 2_815 + .saturating_add(Weight::from_parts(1_232_322, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(11)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -627,14 +627,14 @@ impl pallet_staking::WeightInfo for WeightInfo { fn new_era(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + n * (716 ±0) + v * (3594 ±0)` - // Estimated: `456136 + n * (3566 ±4) + v * (3566 ±40)` - // Minimum execution time: 520_430_000 picoseconds. - Weight::from_parts(527_125_000, 0) + // Estimated: `456136 + n * (3566 ±0) + v * (3566 ±0)` + // Minimum execution time: 528_862_000 picoseconds. + Weight::from_parts(534_620_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 1_974_092 - .saturating_add(Weight::from_parts(64_885_491, 0).saturating_mul(v.into())) - // Standard Error: 196_707 - .saturating_add(Weight::from_parts(18_100_326, 0).saturating_mul(n.into())) + // Standard Error: 2_005_553 + .saturating_add(Weight::from_parts(65_586_008, 0).saturating_mul(v.into())) + // Standard Error: 199_842 + .saturating_add(Weight::from_parts(18_155_389, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(184)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -665,13 +665,13 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `3108 + n * (907 ±0) + v * (391 ±0)` // Estimated: `456136 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 33_917_323_000 picoseconds. - Weight::from_parts(34_173_565_000, 0) + // Minimum execution time: 33_532_110_000 picoseconds. + Weight::from_parts(33_926_321_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 367_135 - .saturating_add(Weight::from_parts(4_696_840, 0).saturating_mul(v.into())) - // Standard Error: 367_135 - .saturating_add(Weight::from_parts(3_889_075, 0).saturating_mul(n.into())) + // Standard Error: 374_134 + .saturating_add(Weight::from_parts(4_627_629, 0).saturating_mul(v.into())) + // Standard Error: 374_134 + .saturating_add(Weight::from_parts(4_068_168, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(179)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -688,11 +688,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `946 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_447_197_000 picoseconds. - Weight::from_parts(13_003_614, 0) + // Minimum execution time: 2_395_956_000 picoseconds. + Weight::from_parts(88_416_870, 0) .saturating_add(Weight::from_parts(0, 3510)) - // Standard Error: 9_738 - .saturating_add(Weight::from_parts(4_953_442, 0).saturating_mul(v.into())) + // Standard Error: 8_731 + .saturating_add(Weight::from_parts(4_750_956, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -703,6 +703,8 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxValidatorsCount` (r:0 w:1) /// Proof: `Staking::MaxValidatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxStakedRewards` (r:0 w:1) + /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::ChillThreshold` (r:0 w:1) /// Proof: `Staking::ChillThreshold` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxNominatorsCount` (r:0 w:1) @@ -713,10 +715,10 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_714_000 picoseconds. - Weight::from_parts(3_956_000, 0) + // Minimum execution time: 3_761_000 picoseconds. + Weight::from_parts(4_013_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(6)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Staking::MinCommission` (r:0 w:1) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -724,6 +726,8 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxValidatorsCount` (r:0 w:1) /// Proof: `Staking::MaxValidatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxStakedRewards` (r:0 w:1) + /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::ChillThreshold` (r:0 w:1) /// Proof: `Staking::ChillThreshold` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxNominatorsCount` (r:0 w:1) @@ -734,10 +738,10 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_361_000 picoseconds. - Weight::from_parts(3_632_000, 0) + // Minimum execution time: 3_325_000 picoseconds. + Weight::from_parts(3_519_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(6)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) @@ -765,8 +769,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1870` // Estimated: `6248` - // Minimum execution time: 65_329_000 picoseconds. - Weight::from_parts(67_247_000, 0) + // Minimum execution time: 63_583_000 picoseconds. + Weight::from_parts(65_917_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(6)) @@ -779,8 +783,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `658` // Estimated: `3510` - // Minimum execution time: 11_760_000 picoseconds. - Weight::from_parts(12_095_000, 0) + // Minimum execution time: 10_975_000 picoseconds. + Weight::from_parts(11_328_000, 0) .saturating_add(Weight::from_parts(0, 3510)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -791,8 +795,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_256_000 picoseconds. - Weight::from_parts(2_378_000, 0) + // Minimum execution time: 1_954_000 picoseconds. + Weight::from_parts(2_081_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs index d9f2d45207b923e3afe661a6021629cb8441970e..aa65a2e9034a7c9f10a3d596db9cf8d167f561ac 100644 --- a/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs +++ b/polkadot/runtime/westend/src/weights/runtime_parachains_coretime.rs @@ -16,11 +16,11 @@ //! Autogenerated weights for `runtime_parachains::coretime` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-r43aesjn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot @@ -32,10 +32,10 @@ // --wasm-execution=compiled // --heap-pages=4096 // --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=runtime_common::coretime -// --chain=rococo-dev +// --pallet=runtime_parachains::coretime +// --chain=westend-dev // --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/rococo/src/weights/ +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -45,28 +45,39 @@ use frame_support::{traits::Get, weights::Weight}; use core::marker::PhantomData; -use runtime_parachains::configuration::{self, WeightInfo as ConfigWeightInfo}; - -/// Weight functions for `runtime_common::coretime`. +/// Weight functions for `runtime_parachains::coretime`. pub struct WeightInfo(PhantomData); -impl runtime_parachains::coretime::WeightInfo for WeightInfo { +impl runtime_parachains::coretime::WeightInfo for WeightInfo { + /// Storage: `Configuration::PendingConfigs` (r:1 w:1) + /// Proof: `Configuration::PendingConfigs` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Configuration::BypassConsistencyCheck` (r:1 w:0) + /// Proof: `Configuration::BypassConsistencyCheck` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParasShared::CurrentSessionIndex` (r:1 w:0) + /// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn request_core_count() -> Weight { - ::WeightInfo::set_config_with_u32() + // Proof Size summary in bytes: + // Measured: `151` + // Estimated: `1636` + // Minimum execution time: 7_486_000 picoseconds. + Weight::from_parts(7_889_000, 0) + .saturating_add(Weight::from_parts(0, 1636)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `CoreTimeAssignmentProvider::CoreDescriptors` (r:1 w:1) - /// Proof: `CoreTimeAssignmentProvider::CoreDescriptors` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `CoreTimeAssignmentProvider::CoreSchedules` (r:0 w:1) - /// Proof: `CoreTimeAssignmentProvider::CoreSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CoretimeAssignmentProvider::CoreDescriptors` (r:1 w:1) + /// Proof: `CoretimeAssignmentProvider::CoreDescriptors` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `CoretimeAssignmentProvider::CoreSchedules` (r:0 w:1) + /// Proof: `CoretimeAssignmentProvider::CoreSchedules` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `s` is `[1, 100]`. fn assign_core(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `76` - // Estimated: `3541` - // Minimum execution time: 6_275_000 picoseconds. - Weight::from_parts(6_883_543, 0) - .saturating_add(Weight::from_parts(0, 3541)) - // Standard Error: 202 - .saturating_add(Weight::from_parts(15_028, 0).saturating_mul(s.into())) + // Measured: `147` + // Estimated: `3612` + // Minimum execution time: 9_409_000 picoseconds. + Weight::from_parts(10_177_115, 0) + .saturating_add(Weight::from_parts(0, 3612)) + // Standard Error: 259 + .saturating_add(Weight::from_parts(13_932, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index 8c2fea69006089992e7af99658e903b6b01f7942..c3aa75a86a45b60492b2b8cf9f4791906ebccdc4 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -39,15 +39,13 @@ use westend_runtime_constants::{ xcm::body::{FELLOWSHIP_ADMIN_INDEX, TREASURER_INDEX}, }; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, DescribeBodyTerminal, DescribeFamily, FrameTransactionalProcessor, - HashedDescription, IsConcrete, MintLocation, OriginToPluralityVoice, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, - UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + FungibleAdapter, HashedDescription, IsConcrete, MintLocation, OriginToPluralityVoice, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, XcmFeeToAccount, }; use xcm_executor::XcmExecutor; @@ -75,8 +73,7 @@ pub type LocationConverter = ( HashedDescription>, ); -#[allow(deprecated)] -pub type LocalAssetTransactor = XcmCurrencyAdapter< +pub type LocalAssetTransactor = FungibleAdapter< // Use this currency: Balances, // Use this currency when it is a fungible asset matching the given location or name: @@ -117,12 +114,16 @@ parameter_types! { pub AssetHub: Location = Parachain(ASSET_HUB_ID).into_location(); pub Collectives: Location = Parachain(COLLECTIVES_ID).into_location(); pub BridgeHub: Location = Parachain(BRIDGE_HUB_ID).into_location(); + pub Encointer: Location = Parachain(ENCOINTER_ID).into_location(); pub People: Location = Parachain(PEOPLE_ID).into_location(); + pub Broker: Location = Parachain(BROKER_ID).into_location(); pub Wnd: AssetFilter = Wild(AllOf { fun: WildFungible, id: AssetId(TokenLocation::get()) }); pub WndForAssetHub: (AssetFilter, Location) = (Wnd::get(), AssetHub::get()); pub WndForCollectives: (AssetFilter, Location) = (Wnd::get(), Collectives::get()); pub WndForBridgeHub: (AssetFilter, Location) = (Wnd::get(), BridgeHub::get()); + pub WndForEncointer: (AssetFilter, Location) = (Wnd::get(), Encointer::get()); pub WndForPeople: (AssetFilter, Location) = (Wnd::get(), People::get()); + pub WndForBroker: (AssetFilter, Location) = (Wnd::get(), Broker::get()); pub MaxInstructions: u32 = 100; pub MaxAssetsIntoHolding: u32 = 64; } @@ -131,7 +132,9 @@ pub type TrustedTeleporters = ( xcm_builder::Case, xcm_builder::Case, xcm_builder::Case, + xcm_builder::Case, xcm_builder::Case, + xcm_builder::Case, ); pub struct OnlyParachains; @@ -262,7 +265,7 @@ pub type LocalPalletOriginToLocation = ( StakingAdminToPlurality, // FellowshipAdmin origin to be used in XCM as a corresponding Plurality `Location` value. FellowshipAdminToPlurality, - // `Treasurer` origin to be used in XCM as a corresponding Plurality `MultiLocation` value. + // `Treasurer` origin to be used in XCM as a corresponding Plurality `Location` value. TreasurerToPlurality, ); diff --git a/polkadot/scripts/build-only-wasm.sh b/polkadot/scripts/build-only-wasm.sh index b6da3319c8214aeca3ca54b76fda87d83077eec5..50b786dab41014dd53cd3e4dea3e02f66fd8b42d 100755 --- a/polkadot/scripts/build-only-wasm.sh +++ b/polkadot/scripts/build-only-wasm.sh @@ -13,6 +13,14 @@ fi WASM_BUILDER_RUNNER="$PROJECT_ROOT/target/release/wbuild-runner/$1" +fl_cargo () { + if command -v forklift >/dev/null 2>&1; then + forklift cargo "$@"; + else + cargo "$@"; + fi +} + if [ -z "$2" ]; then export WASM_TARGET_DIRECTORY=$(pwd) else @@ -22,8 +30,8 @@ fi if [ -d $WASM_BUILDER_RUNNER ]; then export DEBUG=false export OUT_DIR="$PROJECT_ROOT/target/release/build" - cargo run --release --manifest-path="$WASM_BUILDER_RUNNER/Cargo.toml" \ + fl_cargo run --release --manifest-path="$WASM_BUILDER_RUNNER/Cargo.toml" \ | grep -vE "cargo:rerun-if-|Executing build command" else - cargo build --release -p $1 + fl_cargo build --release -p $1 fi diff --git a/polkadot/statement-table/Cargo.toml b/polkadot/statement-table/Cargo.toml index 6403b822ed9b1ad6879698b3909f0f48daf1e765..37b8a99d640a2d23e0d7a532adc0c43b04bba1f2 100644 --- a/polkadot/statement-table/Cargo.toml +++ b/polkadot/statement-table/Cargo.toml @@ -13,3 +13,4 @@ workspace = true parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive"] } sp-core = { path = "../../substrate/primitives/core" } primitives = { package = "polkadot-primitives", path = "../primitives" } +gum = { package = "tracing-gum", path = "../node/gum" } diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index 22bffde5acc11b8fdbea565d054949caf40d788f..2ee6f6a4f781842866728e2105d6c23e2fa1cd70 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -36,6 +36,7 @@ use primitives::{ }; use parity_scale_codec::{Decode, Encode}; +const LOG_TARGET: &str = "parachain::statement-table"; /// Context for the statement table. pub trait Context { @@ -53,9 +54,6 @@ pub trait Context { /// get the digest of a candidate. fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest; - /// get the group of a candidate. - fn candidate_group(candidate: &Self::Candidate) -> Self::GroupId; - /// Whether a authority is a member of a group. /// Members are meant to submit candidates and vote on validity. fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool; @@ -342,13 +340,13 @@ impl Table { pub fn import_statement( &mut self, context: &Ctx, + group_id: Ctx::GroupId, statement: SignedStatement, ) -> Option> { let SignedStatement { statement, signature, sender: signer } = statement; - let res = match statement { Statement::Seconded(candidate) => - self.import_candidate(context, signer.clone(), candidate, signature), + self.import_candidate(context, signer.clone(), candidate, signature, group_id), Statement::Valid(digest) => self.validity_vote(context, signer.clone(), digest, ValidityVote::Valid(signature)), }; @@ -387,9 +385,10 @@ impl Table { authority: Ctx::AuthorityId, candidate: Ctx::Candidate, signature: Ctx::Signature, + group: Ctx::GroupId, ) -> ImportResult { - let group = Ctx::candidate_group(&candidate); if !context.is_member_of(&authority, &group) { + gum::debug!(target: LOG_TARGET, authority = ?authority, group = ?group, "New `Misbehavior::UnauthorizedStatement`, candidate backed by validator that doesn't belong to expected group" ); return Err(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { statement: SignedStatement { signature, @@ -634,10 +633,6 @@ mod tests { Digest(candidate.1) } - fn candidate_group(candidate: &Candidate) -> GroupId { - GroupId(candidate.0) - } - fn is_member_of(&self, authority: &AuthorityId, group: &GroupId) -> bool { self.authorities.get(authority).map(|v| v == group).unwrap_or(false) } @@ -675,10 +670,10 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement_a); + table.import_statement(&context, GroupId(2), statement_a); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); - table.import_statement(&context, statement_b); + table.import_statement(&context, GroupId(2), statement_b); assert_eq!( table.detected_misbehavior[&AuthorityId(1)][0], Misbehavior::MultipleCandidates(MultipleCandidates { @@ -711,10 +706,10 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement_a); + table.import_statement(&context, GroupId(2), statement_a); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); - table.import_statement(&context, statement_b); + table.import_statement(&context, GroupId(2), statement_b); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); } @@ -735,7 +730,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert_eq!( table.detected_misbehavior[&AuthorityId(1)][0], @@ -769,7 +764,7 @@ mod tests { }; let candidate_a_digest = Digest(100); - table.import_statement(&context, candidate_a); + table.import_statement(&context, GroupId(2), candidate_a); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); @@ -779,7 +774,7 @@ mod tests { signature: Signature(2), sender: AuthorityId(2), }; - table.import_statement(&context, bad_validity_vote); + table.import_statement(&context, GroupId(3), bad_validity_vote); assert_eq!( table.detected_misbehavior[&AuthorityId(2)][0], @@ -811,7 +806,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); let invalid_statement = SignedStatement { @@ -820,7 +815,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, invalid_statement); + table.import_statement(&context, GroupId(2), invalid_statement); assert!(table.detected_misbehavior.contains_key(&AuthorityId(1))); } @@ -842,7 +837,7 @@ mod tests { }; let candidate_digest = Digest(100); - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); let extra_vote = SignedStatement { @@ -851,7 +846,7 @@ mod tests { sender: AuthorityId(1), }; - table.import_statement(&context, extra_vote); + table.import_statement(&context, GroupId(2), extra_vote); assert_eq!( table.detected_misbehavior[&AuthorityId(1)][0], Misbehavior::ValidityDoubleVote(ValidityDoubleVote::IssuedAndValidity( @@ -910,7 +905,7 @@ mod tests { }; let candidate_digest = Digest(100); - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); assert!(table.attested_candidate(&candidate_digest, &context, 2).is_none()); @@ -921,7 +916,7 @@ mod tests { sender: AuthorityId(2), }; - table.import_statement(&context, vote); + table.import_statement(&context, GroupId(2), vote); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); assert!(table.attested_candidate(&candidate_digest, &context, 2).is_some()); } @@ -944,7 +939,7 @@ mod tests { }; let summary = table - .import_statement(&context, statement) + .import_statement(&context, GroupId(2), statement) .expect("candidate import to give summary"); assert_eq!(summary.candidate, Digest(100)); @@ -971,7 +966,7 @@ mod tests { }; let candidate_digest = Digest(100); - table.import_statement(&context, statement); + table.import_statement(&context, GroupId(2), statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); let vote = SignedStatement { @@ -980,8 +975,9 @@ mod tests { sender: AuthorityId(2), }; - let summary = - table.import_statement(&context, vote).expect("candidate vote to give summary"); + let summary = table + .import_statement(&context, GroupId(2), vote) + .expect("candidate vote to give summary"); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); diff --git a/polkadot/statement-table/src/lib.rs b/polkadot/statement-table/src/lib.rs index d4629330ac01512e2dc2b0f441d2302f0fd38624..3740d15cc4f326962b962557e61ca14e9163de7d 100644 --- a/polkadot/statement-table/src/lib.rs +++ b/polkadot/statement-table/src/lib.rs @@ -35,8 +35,8 @@ pub use generic::{Config, Context, Table}; pub mod v2 { use crate::generic; use primitives::{ - CandidateHash, CommittedCandidateReceipt, CompactStatement as PrimitiveStatement, Id, - ValidatorIndex, ValidatorSignature, + CandidateHash, CommittedCandidateReceipt, CompactStatement as PrimitiveStatement, + CoreIndex, ValidatorIndex, ValidatorSignature, }; /// Statements about candidates on the network. @@ -59,7 +59,7 @@ pub mod v2 { >; /// A summary of import of a statement. - pub type Summary = generic::Summary; + pub type Summary = generic::Summary; impl<'a> From<&'a Statement> for PrimitiveStatement { fn from(s: &'a Statement) -> PrimitiveStatement { diff --git a/polkadot/utils/generate-bags/Cargo.toml b/polkadot/utils/generate-bags/Cargo.toml index b255b7ab64d05790d5e278ea9e74664d9236a5fc..c0e9bd332df7ccca511bd199221405ce4d43d025 100644 --- a/polkadot/utils/generate-bags/Cargo.toml +++ b/polkadot/utils/generate-bags/Cargo.toml @@ -10,7 +10,7 @@ description = "CLI to generate voter bags for Polkadot runtimes" workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } generate-bags = { path = "../../../substrate/utils/frame/generate-bags" } sp-io = { path = "../../../substrate/primitives/io" } diff --git a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml index f8190e6aefa941f328a31c46b21bda7d8f089872..ddc5af97a166af253e660d0523b7ac6e357c367c 100644 --- a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml +++ b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml @@ -18,6 +18,6 @@ sp-tracing = { path = "../../../../substrate/primitives/tracing" } frame-system = { path = "../../../../substrate/frame/system" } sp-core = { path = "../../../../substrate/primitives/core" } -clap = { version = "4.4.18", features = ["derive"] } -log = "0.4.17" +clap = { version = "4.5.1", features = ["derive"] } +log = { workspace = true, default-features = true } tokio = { version = "1.24.2", features = ["macros"] } diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index 41b79051bbf01d5af62bd0646e77e146a8498d93..f9ccfb9833a6b276f001491ff29daa183f0f8e22 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -14,11 +14,11 @@ array-bytes = "6.1" bounded-collections = { version = "0.2.0", default-features = false, features = ["serde"] } derivative = { version = "2.2.0", default-features = false, features = ["use_core"] } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.17", default-features = false } +log = { workspace = true } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } sp-weights = { path = "../../substrate/primitives/weights", default-features = false, features = ["serde"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive", "rc"] } +serde = { features = ["alloc", "derive", "rc"], workspace = true } schemars = { version = "0.8.13", default-features = true, optional = true } xcm-procedural = { path = "procedural" } environmental = { version = "1.1.4", default-features = false } diff --git a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml index adeacddf90c1522a9505b7126365e2791fbc88e9..80f2d1deedf724c28ca71508a2b21411b57096ee 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -24,7 +24,7 @@ xcm-executor = { package = "staging-xcm-executor", path = "../xcm-executor", def frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false } xcm = { package = "staging-xcm", path = "..", default-features = false } xcm-builder = { package = "staging-xcm-builder", path = "../xcm-builder", default-features = false } -log = "0.4.17" +log = { workspace = true, default-features = true } [dev-dependencies] pallet-balances = { path = "../../../substrate/frame/balances" } diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs index e96ec48fcba46f2aaf668445ad25ecc5888a7d7a..4b77199069d341320a67f196719604cedcc35157 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs @@ -64,13 +64,16 @@ benchmarks_instance_pallet! { transfer_asset { let (sender_account, sender_location) = account_and_location::(1); let asset = T::get_asset(); - let assets: Assets = vec![ asset.clone() ].into(); + let assets: Assets = vec![asset.clone()].into(); // this xcm doesn't use holding let dest_location = T::valid_destination()?; let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap(); >::deposit_asset(&asset, &sender_location, None).unwrap(); + // We deposit the asset twice so we have enough for ED after transferring + >::deposit_asset(&asset, &sender_location, None).unwrap(); + let sender_account_balance_before = T::TransactAsset::balance(&sender_account); assert!(T::TransactAsset::balance(&dest_account).is_zero()); let mut executor = new_executor::(sender_location); @@ -79,7 +82,7 @@ benchmarks_instance_pallet! { }: { executor.bench_process(xcm)?; } verify { - assert!(T::TransactAsset::balance(&sender_account).is_zero()); + assert!(T::TransactAsset::balance(&sender_account) < sender_account_balance_before); assert!(!T::TransactAsset::balance(&dest_account).is_zero()); } @@ -93,11 +96,12 @@ benchmarks_instance_pallet! { &dest_location, FeeReason::TransferReserveAsset ); - let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let asset = T::get_asset(); >::deposit_asset(&asset, &sender_location, None).unwrap(); - assert!(T::TransactAsset::balance(&sender_account) > sender_account_balance_before); + // We deposit the asset twice so we have enough for ED after transferring + >::deposit_asset(&asset, &sender_location, None).unwrap(); + let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let assets: Assets = vec![asset].into(); assert!(T::TransactAsset::balance(&dest_account).is_zero()); diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs index fe3ee81f9d44c1a3717b07670ee71cd6f967c8ca..637446832fdca7d836f4df6b8565a46682f96213 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/mock.rs @@ -103,8 +103,7 @@ impl xcm_executor::traits::MatchesFungible for MatchAnyFungible { } // Use balances as the asset transactor. -#[allow(deprecated)] -pub type AssetTransactor = xcm_builder::CurrencyAdapter< +pub type AssetTransactor = xcm_builder::FungibleAdapter< Balances, MatchAnyFungible, AccountIdConverter, @@ -192,8 +191,7 @@ impl xcm_balances_benchmark::Config for Test { type TrustedReserve = TrustedReserve; fn get_asset() -> Asset { - let amount = - >::minimum_balance() as u128; + let amount = 1_000_000_000_000; Asset { id: AssetId(Here.into()), fun: Fungible(amount) } } } diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs index 6ce8d3e99e8e54114048ff35d2c2a7c38b01b9c4..63ed0ac0ca736450077a4677d29d65a81e9eaf3b 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/lib.rs @@ -22,10 +22,8 @@ use codec::Encode; use frame_benchmarking::{account, BenchmarkError}; use sp_std::prelude::*; use xcm::latest::prelude::*; -use xcm_executor::{ - traits::{ConvertLocation, FeeReason}, - Config as XcmConfig, FeesMode, -}; +use xcm_builder::EnsureDelivery; +use xcm_executor::{traits::ConvertLocation, Config as XcmConfig}; pub mod fungible; pub mod generic; @@ -114,29 +112,3 @@ pub fn account_and_location(index: u32) -> (T::AccountId, Location) { (account, location) } - -/// Trait for a type which ensures all requirements for successful delivery with XCM transport -/// layers. -pub trait EnsureDelivery { - /// Prepare all requirements for successful `XcmSender: SendXcm` passing (accounts, balances, - /// channels ...). Returns: - /// - possible `FeesMode` which is expected to be set to executor - /// - possible `Assets` which are expected to be subsume to the Holding Register - fn ensure_successful_delivery( - origin_ref: &Location, - dest: &Location, - fee_reason: FeeReason, - ) -> (Option, Option); -} - -/// `()` implementation does nothing which means no special requirements for environment. -impl EnsureDelivery for () { - fn ensure_successful_delivery( - _origin_ref: &Location, - _dest: &Location, - _fee_reason: FeeReason, - ) -> (Option, Option) { - // doing nothing - (None, None) - } -} diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index dc9b3c0e20d352bd3240f9e002919be39e6e4464..4840b6127f55425de91eea85099649d4dfda0881 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -13,8 +13,8 @@ workspace = true bounded-collections = { version = "0.2.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +log = { workspace = true } frame-support = { path = "../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../substrate/frame/system", default-features = false } diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index c7d8fb24e9df320f8439616591c467f3f6d7aa1e..e3ea2fb8c06dfb0f3b5e03e4434993bd0d7c35ff 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -17,21 +17,26 @@ use super::*; use bounded_collections::{ConstU32, WeakBoundedVec}; use frame_benchmarking::{benchmarks, whitelisted_caller, BenchmarkError, BenchmarkResult}; -use frame_support::{traits::Currency, weights::Weight}; +use frame_support::{ + traits::fungible::{Inspect, Mutate}, + weights::Weight, +}; use frame_system::RawOrigin; use sp_std::prelude::*; use xcm::{latest::prelude::*, v2}; +use xcm_builder::EnsureDelivery; +use xcm_executor::traits::FeeReason; type RuntimeOrigin = ::RuntimeOrigin; -// existential deposit multiplier -const ED_MULTIPLIER: u32 = 100; - /// Pallet we're benchmarking here. pub struct Pallet(crate::Pallet); /// Trait that must be implemented by runtime to be able to benchmark pallet properly. pub trait Config: crate::Config { + /// Helper that ensures successful delivery for extrinsics/benchmarks which need `SendXcm`. + type DeliveryHelper: EnsureDelivery; + /// A `Location` that can be reached via `XcmRouter`. Used only in benchmarks. /// /// If `None`, the benchmarks that depend on a reachable destination will be skipped. @@ -107,23 +112,29 @@ benchmarks! { }.into(); let assets: Assets = asset.into(); - let existential_deposit = T::ExistentialDeposit::get(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - assert!(balance >= transferred_amount); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - // verify initial balance - assert_eq!(pallet_balances::Pallet::::free_balance(&caller), balance); - + let caller: T::AccountId = whitelisted_caller(); let send_origin = RawOrigin::Signed(caller.clone()); let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmTeleportFilter::contains(&(origin_location, assets.clone().into_inner())) { + if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &origin_location, + &destination, + FeeReason::ChargeFees, + ); + + // Actual balance (e.g. `ensure_successful_delivery` could drip delivery fees, ...) + let balance = as Inspect<_>>::balance(&caller); + // Add transferred_amount to origin + as Mutate<_>>::mint_into(&caller, transferred_amount)?; + // verify initial balance + let balance = balance + transferred_amount; + assert_eq!( as Inspect<_>>::balance(&caller), balance); + let recipient = [0u8; 32]; let versioned_dest: VersionedLocation = destination.into(); let versioned_beneficiary: VersionedLocation = @@ -132,7 +143,7 @@ benchmarks! { }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) verify { // verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees) - assert!(pallet_balances::Pallet::::free_balance(&caller) <= balance - transferred_amount); + assert!( as Inspect<_>>::balance(&caller) <= balance - transferred_amount); } reserve_transfer_assets { @@ -146,23 +157,29 @@ benchmarks! { }.into(); let assets: Assets = asset.into(); - let existential_deposit = T::ExistentialDeposit::get(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - assert!(balance >= transferred_amount); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - // verify initial balance - assert_eq!(pallet_balances::Pallet::::free_balance(&caller), balance); - + let caller: T::AccountId = whitelisted_caller(); let send_origin = RawOrigin::Signed(caller.clone()); let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmReserveTransferFilter::contains(&(origin_location, assets.clone().into_inner())) { + if !T::XcmReserveTransferFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &origin_location, + &destination, + FeeReason::ChargeFees, + ); + + // Actual balance (e.g. `ensure_successful_delivery` could drip delivery fees, ...) + let balance = as Inspect<_>>::balance(&caller); + // Add transferred_amount to origin + as Mutate<_>>::mint_into(&caller, transferred_amount)?; + // verify initial balance + let balance = balance + transferred_amount; + assert_eq!( as Inspect<_>>::balance(&caller), balance); + let recipient = [0u8; 32]; let versioned_dest: VersionedLocation = destination.into(); let versioned_beneficiary: VersionedLocation = @@ -171,7 +188,7 @@ benchmarks! { }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) verify { // verify balance after transfer, decreased by transferred amount (+ maybe XCM delivery fees) - assert!(pallet_balances::Pallet::::free_balance(&caller) <= balance - transferred_amount); + assert!( as Inspect<_>>::balance(&caller) <= balance - transferred_amount); } transfer_assets { diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 55154198a9b2d3ed537de187c489632b18e76610..5e1a3e55f9b62b67bb3cedb6a279a1c4c9046405 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -29,7 +29,7 @@ pub mod migration; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use frame_support::{ - dispatch::GetDispatchInfo, + dispatch::{DispatchErrorWithPostInfo, GetDispatchInfo, WithPostDispatchInfo}, pallet_prelude::*, traits::{ Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency, @@ -62,6 +62,9 @@ use xcm_executor::{ AssetsInHolding, }; +#[cfg(any(feature = "try-runtime", test))] +use sp_runtime::TryRuntimeError; + pub trait WeightInfo { fn send() -> Weight; fn teleport_assets() -> Weight; @@ -288,22 +291,38 @@ pub mod pallet { origin: OriginFor, message: Box::RuntimeCall>>, max_weight: Weight, - ) -> Result { - let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; - let mut hash = message.using_encoded(sp_io::hashing::blake2_256); - let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; - let value = (origin_location, message); - ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); - let (origin_location, message) = value; - let outcome = T::XcmExecutor::prepare_and_execute( - origin_location, - message, - &mut hash, - max_weight, - max_weight, - ); + ) -> Result { + log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight); + let outcome = (|| { + let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?; + let mut hash = message.using_encoded(sp_io::hashing::blake2_256); + let message = (*message).try_into().map_err(|()| Error::::BadVersion)?; + let value = (origin_location, message); + ensure!(T::XcmExecuteFilter::contains(&value), Error::::Filtered); + let (origin_location, message) = value; + Ok(T::XcmExecutor::prepare_and_execute( + origin_location, + message, + &mut hash, + max_weight, + max_weight, + )) + })() + .map_err(|e: DispatchError| { + e.with_weight(::execute()) + })?; + Self::deposit_event(Event::Attempted { outcome: outcome.clone() }); - Ok(outcome) + let weight_used = outcome.weight_used(); + outcome.ensure_complete().map_err(|error| { + log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error); + Error::::LocalExecutionIncomplete.with_weight( + weight_used.saturating_add( + ::execute(), + ), + ) + })?; + Ok(weight_used) } } @@ -464,6 +483,8 @@ pub mod pallet { FeesPaid { paying: Location, fees: Assets }, /// Some assets have been claimed from an asset trap AssetsClaimed { hash: H256, origin: Location, assets: VersionedAssets }, + /// A XCM version migration finished. + VersionMigrationFinished { version: XcmVersion }, } #[pallet::origin] @@ -765,6 +786,9 @@ pub mod pallet { // Consume 10% of block at most let max_weight = T::BlockWeights::get().max_block / 10; let (w, maybe_migration) = Self::check_xcm_version_change(migration, max_weight); + if maybe_migration.is_none() { + Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION }); + } CurrentMigration::::set(maybe_migration); weight_used.saturating_accrue(w); } @@ -791,6 +815,11 @@ pub mod pallet { } weight_used } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { + Self::do_try_state() + } } pub mod migrations { @@ -996,9 +1025,6 @@ pub mod pallet { /// No more than `max_weight` will be used in its attempted execution. If this is less than /// the maximum amount of weight that the message could take to be executed, then no /// execution attempt will be made. - /// - /// NOTE: A successful return to this does *not* imply that the `msg` was executed - /// successfully to completion; only that it was attempted. #[pallet::call_index(3)] #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))] pub fn execute( @@ -1006,13 +1032,8 @@ pub mod pallet { message: Box::RuntimeCall>>, max_weight: Weight, ) -> DispatchResultWithPostInfo { - log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight); - let outcome = >::execute(origin, message, max_weight)?; - let weight_used = outcome.weight_used(); - outcome.ensure_complete().map_err(|error| { - log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error); - Error::::LocalExecutionIncomplete - })?; + let weight_used = + >::execute(origin, message, max_weight)?; Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into()) } @@ -2387,6 +2408,48 @@ impl Pallet { Self::deposit_event(Event::FeesPaid { paying: location, fees: assets }); Ok(()) } + + /// Ensure the correctness of the state of this pallet. + /// + /// This should be valid before and after each state transition of this pallet. + /// + /// ## Invariants + /// + /// All entries stored in the `SupportedVersion` / `VersionNotifiers` / `VersionNotifyTargets` + /// need to be migrated to the `XCM_VERSION`. If they are not, then `CurrentMigration` has to be + /// set. + #[cfg(any(feature = "try-runtime", test))] + pub fn do_try_state() -> Result<(), TryRuntimeError> { + // if migration has been already scheduled, everything is ok and data will be eventually + // migrated + if CurrentMigration::::exists() { + return Ok(()) + } + + // if migration has NOT been scheduled yet, we need to check all operational data + for v in 0..XCM_VERSION { + ensure!( + SupportedVersion::::iter_prefix(v).next().is_none(), + TryRuntimeError::Other( + "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`" + ) + ); + ensure!( + VersionNotifiers::::iter_prefix(v).next().is_none(), + TryRuntimeError::Other( + "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`" + ) + ); + ensure!( + VersionNotifyTargets::::iter_prefix(v).next().is_none(), + TryRuntimeError::Other( + "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`" + ) + ); + } + + Ok(()) + } } pub struct LockTicket { diff --git a/polkadot/xcm/pallet-xcm/src/migration.rs b/polkadot/xcm/pallet-xcm/src/migration.rs index 2793afcc910484948f7d3a611c88b2ca59beb766..018436aa3c93a9291a99820a57118d119f5a0828 100644 --- a/polkadot/xcm/pallet-xcm/src/migration.rs +++ b/polkadot/xcm/pallet-xcm/src/migration.rs @@ -14,7 +14,9 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -use crate::{Config, Pallet, VersionNotifyTargets}; +use crate::{ + pallet::CurrentMigration, Config, Pallet, VersionMigrationStage, VersionNotifyTargets, +}; use frame_support::{ pallet_prelude::*, traits::{OnRuntimeUpgrade, StorageVersion}, @@ -73,3 +75,16 @@ pub mod v1 { ::DbWeight, >; } + +/// When adding a new XCM version, we need to run this migration for `pallet_xcm` to ensure that all +/// previously stored data with subkey prefix `XCM_VERSION-1` (and below) are migrated to the +/// `XCM_VERSION`. +/// +/// NOTE: This migration can be permanently added to the runtime migrations. +pub struct MigrateToLatestXcmVersion(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for MigrateToLatestXcmVersion { + fn on_runtime_upgrade() -> Weight { + CurrentMigration::::put(VersionMigrationStage::default()); + T::DbWeight::get().writes(1) + } +} diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 434dac1659b50085f4e4f863e77c0cbe1b38af68..17c181387dde480df9ae76798aa6649d1637dcf5 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -30,13 +30,11 @@ use sp_core::H256; use sp_runtime::{traits::IdentityLookup, AccountId32, BuildStorage}; pub use sp_std::cell::RefCell; use xcm::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, DescribeAllTerminal, FixedRateOfFungible, FixedWeightBounds, - FrameTransactionalProcessor, FungiblesAdapter, HashedDescription, IsConcrete, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, HashedDescription, IsConcrete, MatchedConvertedConcreteId, NoChecking, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, XcmFeeManagerFromComponents, XcmFeeToAccount, }; @@ -424,9 +422,8 @@ pub type ForeignAssetsConvertedConcreteId = MatchedConvertedConcreteId< JustTry, >; -#[allow(deprecated)] pub type AssetTransactors = ( - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>, + FungibleAdapter, SovereignAccountOf, AccountId, ()>, FungiblesAdapter< AssetsPallet, ForeignAssetsConvertedConcreteId, @@ -566,8 +563,30 @@ impl pallet_test_notifier::Config for Test { type RuntimeCall = RuntimeCall; } +#[cfg(feature = "runtime-benchmarks")] +pub struct TestDeliveryHelper; +#[cfg(feature = "runtime-benchmarks")] +impl xcm_builder::EnsureDelivery for TestDeliveryHelper { + fn ensure_successful_delivery( + origin_ref: &Location, + _dest: &Location, + _fee_reason: xcm_executor::traits::FeeReason, + ) -> (Option, Option) { + use xcm_executor::traits::ConvertLocation; + let account = SovereignAccountOf::convert_location(origin_ref).expect("Valid location"); + // Give the existential deposit at least + let balance = ExistentialDeposit::get(); + let _ = >::make_free_balance_be( + &account, balance, + ); + (None, None) + } +} + #[cfg(feature = "runtime-benchmarks")] impl super::benchmarking::Config for Test { + type DeliveryHelper = TestDeliveryHelper; + fn reachable_dest() -> Option { Some(Parachain(1000).into()) } diff --git a/polkadot/xcm/pallet-xcm/src/tests/mod.rs b/polkadot/xcm/pallet-xcm/src/tests/mod.rs index 5f9c86ed7b3f0bd99dc9064e1c53ef9872a799ce..28c7d197443b070423bfb694593c6feb0d471534 100644 --- a/polkadot/xcm/pallet-xcm/src/tests/mod.rs +++ b/polkadot/xcm/pallet-xcm/src/tests/mod.rs @@ -19,11 +19,13 @@ pub(crate) mod assets_transfer; use crate::{ - mock::*, AssetTraps, CurrentMigration, Error, LatestVersionedLocation, Queries, QueryStatus, + mock::*, pallet::SupportedVersion, AssetTraps, Config, CurrentMigration, Error, + ExecuteControllerWeightInfo, LatestVersionedLocation, Pallet, Queries, QueryStatus, VersionDiscoveryQueue, VersionMigrationStage, VersionNotifiers, VersionNotifyTargets, + WeightInfo, }; use frame_support::{ - assert_noop, assert_ok, + assert_err_ignore_postinfo, assert_noop, assert_ok, traits::{Currency, Hooks}, weights::Weight, }; @@ -449,19 +451,19 @@ fn trapped_assets_can_be_claimed() { assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE + SEND_AMOUNT); assert_eq!(AssetTraps::::iter().collect::>(), vec![]); - let weight = BaseXcmWeight::get() * 3; - assert_ok!(>::execute( - RuntimeOrigin::signed(ALICE), - Box::new(VersionedXcm::from(Xcm(vec![ - ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, - buy_execution((Here, SEND_AMOUNT)), - DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, - ]))), - weight - )); - let outcome = - Outcome::Incomplete { used: BaseXcmWeight::get(), error: XcmError::UnknownClaim }; - assert_eq!(last_event(), RuntimeEvent::XcmPallet(crate::Event::Attempted { outcome })); + // Can't claim twice. + assert_err_ignore_postinfo!( + XcmPallet::execute( + RuntimeOrigin::signed(ALICE), + Box::new(VersionedXcm::from(Xcm(vec![ + ClaimAsset { assets: (Here, SEND_AMOUNT).into(), ticket: Here.into() }, + buy_execution((Here, SEND_AMOUNT)), + DepositAsset { assets: AllCounted(1).into(), beneficiary: dest }, + ]))), + weight + ), + Error::::LocalExecutionIncomplete + ); }); } @@ -494,11 +496,14 @@ fn incomplete_execute_reverts_side_effects() { // all effects are reverted and balances unchanged for either sender or receiver assert_eq!(Balances::total_balance(&ALICE), INITIAL_BALANCE); assert_eq!(Balances::total_balance(&BOB), INITIAL_BALANCE); + assert_eq!( result, Err(sp_runtime::DispatchErrorWithPostInfo { post_info: frame_support::dispatch::PostDispatchInfo { - actual_weight: None, + actual_weight: Some( + as ExecuteControllerWeightInfo>::execute() + weight + ), pays_fee: frame_support::dispatch::Pays::Yes, }, error: sp_runtime::DispatchError::Module(sp_runtime::ModuleError { @@ -1113,3 +1118,83 @@ fn get_and_wrap_version_works() { assert_eq!(VersionDiscoveryQueue::::get().into_inner(), vec![(remote_b.into(), 2)]); }) } + +#[test] +fn multistage_migration_works() { + new_test_ext_with_balances(vec![]).execute_with(|| { + // An entry from a previous runtime with v3 XCM. + let v3_location = VersionedLocation::V3(xcm::v3::Junction::Parachain(1001).into()); + let v3_version = xcm::v3::VERSION; + SupportedVersion::::insert(v3_version, v3_location.clone(), v3_version); + VersionNotifiers::::insert(v3_version, v3_location.clone(), 1); + VersionNotifyTargets::::insert( + v3_version, + v3_location, + (70, Weight::zero(), v3_version), + ); + // A version to advertise. + AdvertisedXcmVersion::set(4); + + // check `try-state` + assert!(Pallet::::do_try_state().is_err()); + + // closure simulates a multistage migration process + let migrate = |expected_cycle_count| { + // A runtime upgrade which alters the version does send notifications. + CurrentMigration::::put(VersionMigrationStage::default()); + let mut maybe_migration = CurrentMigration::::take(); + let mut counter = 0; + let mut weight_used = Weight::zero(); + while let Some(migration) = maybe_migration.take() { + counter += 1; + let (w, m) = XcmPallet::check_xcm_version_change(migration, Weight::zero()); + maybe_migration = m; + weight_used.saturating_accrue(w); + } + assert_eq!(counter, expected_cycle_count); + weight_used + }; + + // run migration for the first time + let _ = migrate(4); + + // check xcm sent + assert_eq!( + take_sent_xcm(), + vec![( + Parachain(1001).into(), + Xcm(vec![QueryResponse { + query_id: 70, + max_weight: Weight::zero(), + response: Response::Version(AdvertisedXcmVersion::get()), + querier: None, + }]) + ),] + ); + + // check migrated data + assert_eq!( + SupportedVersion::::iter().collect::>(), + vec![(XCM_VERSION, Parachain(1001).into_versioned(), v3_version),] + ); + assert_eq!( + VersionNotifiers::::iter().collect::>(), + vec![(XCM_VERSION, Parachain(1001).into_versioned(), 1),] + ); + assert_eq!( + VersionNotifyTargets::::iter().collect::>(), + vec![(XCM_VERSION, Parachain(1001).into_versioned(), (70, Weight::zero(), 4)),] + ); + + // run migration again to check it can run multiple time without any harm or double sending + // messages. + let weight_used = migrate(1); + assert_eq!(weight_used, 1_u8 * ::WeightInfo::already_notified_target()); + + // check no xcm sent + assert_eq!(take_sent_xcm(), vec![]); + + // check `try-state` + assert!(Pallet::::do_try_state().is_ok()); + }) +} diff --git a/polkadot/xcm/procedural/Cargo.toml b/polkadot/xcm/procedural/Cargo.toml index f87347f12dc29161bfe2ab71f5cce69530b94cfe..ca9fb351bd3cad1f805106e405cfdcc496c9d8a8 100644 --- a/polkadot/xcm/procedural/Cargo.toml +++ b/polkadot/xcm/procedural/Cargo.toml @@ -15,8 +15,8 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = "2.0.48" +quote = { workspace = true } +syn = { workspace = true } Inflector = "0.11.4" [dev-dependencies] diff --git a/polkadot/xcm/src/lib.rs b/polkadot/xcm/src/lib.rs index 561d5175b419376449759a92e61e0a2f12ae0294..c90ad25c0d83d0bf155a766cf341d5b646e76800 100644 --- a/polkadot/xcm/src/lib.rs +++ b/polkadot/xcm/src/lib.rs @@ -494,7 +494,7 @@ pub trait WrapVersion { } /// Check and return the `Version` that should be used for the `Xcm` datum for the destination -/// `MultiLocation`, which will interpret it. +/// `Location`, which will interpret it. pub trait GetVersion { fn get_version_for(dest: &latest::Location) -> Option; } diff --git a/polkadot/xcm/src/v2/mod.rs b/polkadot/xcm/src/v2/mod.rs index 188b7f0b5c9384b7395ed08b27c4c8378719be8f..347f3f2c29206222ca546b7d543409da83cbedcf 100644 --- a/polkadot/xcm/src/v2/mod.rs +++ b/polkadot/xcm/src/v2/mod.rs @@ -1134,7 +1134,10 @@ impl TryFrom> for Instruction Self::UnsubscribeVersion, - _ => return Err(()), + i => { + log::debug!(target: "xcm::v3tov2", "`{i:?}` not supported by v2"); + return Err(()); + }, }) } } diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 30010fc2105b8f47a9a943dbd72ec9490cd49f7a..10726b0f511909c5cc4509746c09b731d32b2faf 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -23,7 +23,7 @@ sp-weights = { path = "../../../substrate/primitives/weights", default-features frame-support = { path = "../../../substrate/frame/support", default-features = false } frame-system = { path = "../../../substrate/frame/system", default-features = false } pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } # Polkadot dependencies polkadot-parachain-primitives = { path = "../../parachain", default-features = false } diff --git a/polkadot/xcm/xcm-builder/src/controller.rs b/polkadot/xcm/xcm-builder/src/controller.rs index 8ead18b5bd7fb4d64967123c13246ce60f46e437..ba2b1fb44b8eeb0916fdec24d97e36121bc18c8a 100644 --- a/polkadot/xcm/xcm-builder/src/controller.rs +++ b/polkadot/xcm/xcm-builder/src/controller.rs @@ -18,7 +18,10 @@ //! Controller traits defined in this module are high-level traits that will rely on other traits //! from `xcm-executor` to perform their tasks. -use frame_support::pallet_prelude::DispatchError; +use frame_support::{ + dispatch::{DispatchErrorWithPostInfo, WithPostDispatchInfo}, + pallet_prelude::DispatchError, +}; use sp_std::boxed::Box; use xcm::prelude::*; pub use xcm_executor::traits::QueryHandler; @@ -52,7 +55,8 @@ pub trait ExecuteController { /// Weight information for ExecuteController functions. type WeightInfo: ExecuteControllerWeightInfo; - /// Attempt to execute an XCM locally, and return the outcome. + /// Attempt to execute an XCM locally, returns Ok with the weight consumed if the execution + /// complete successfully, Err otherwise. /// /// # Parameters /// @@ -63,7 +67,7 @@ pub trait ExecuteController { origin: Origin, message: Box>, max_weight: Weight, - ) -> Result; + ) -> Result; } /// Weight functions needed for [`SendController`]. @@ -137,8 +141,9 @@ impl ExecuteController for () { _origin: Origin, _message: Box>, _max_weight: Weight, - ) -> Result { - Ok(Outcome::Error { error: XcmError::Unimplemented }) + ) -> Result { + Err(DispatchError::Other("ExecuteController::execute not implemented") + .with_weight(Weight::zero())) } } diff --git a/polkadot/xcm/xcm-builder/src/currency_adapter.rs b/polkadot/xcm/xcm-builder/src/currency_adapter.rs index fe26b7319bb18b2d4ad33774d389afd0f834eb50..24261ac06583c4ca284f52071a4ee80afae2a6ec 100644 --- a/polkadot/xcm/xcm-builder/src/currency_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/currency_adapter.rs @@ -170,7 +170,7 @@ impl< } fn can_check_out(_dest: &Location, what: &Asset, _context: &XcmContext) -> Result { - log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what); + log::trace!(target: "xcm::currency_adapter", "can_check_out dest: {:?}, what: {:?}", _dest, what); let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?; match CheckedAccount::get() { Some((checked_account, MintLocation::Local)) => diff --git a/polkadot/xcm/xcm-builder/src/fungible_adapter.rs b/polkadot/xcm/xcm-builder/src/fungible_adapter.rs index 7bea8cdf957e169585c182d3a3489519ee2a14ed..21c828922b333e85e9332d16c768aef0d34cbd9f 100644 --- a/polkadot/xcm/xcm-builder/src/fungible_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/fungible_adapter.rs @@ -151,7 +151,7 @@ impl< fn can_check_out(_dest: &Location, what: &Asset, _context: &XcmContext) -> XcmResult { log::trace!( target: "xcm::fungible_adapter", - "check_out dest: {:?}, what: {:?}", + "can_check_out dest: {:?}, what: {:?}", _dest, what ); @@ -204,7 +204,7 @@ impl< ) -> result::Result { log::trace!( target: "xcm::fungible_adapter", - "deposit_asset what: {:?}, who: {:?}", + "withdraw_asset what: {:?}, who: {:?}", what, who, ); let amount = Matcher::matches_fungible(what).ok_or(MatchError::AssetNotHandled)?; diff --git a/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs b/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs index 4574d5ed4c682c7bb62b6f20b6f9397dc37e4098..b4c418ebf1c9cf7d9d26540e998f966665993585 100644 --- a/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs +++ b/polkadot/xcm/xcm-builder/src/fungibles_adapter.rs @@ -235,7 +235,7 @@ impl< fn can_check_out(_origin: &Location, what: &Asset, _context: &XcmContext) -> XcmResult { log::trace!( target: "xcm::fungibles_adapter", - "can_check_in origin: {:?}, what: {:?}", + "can_check_out origin: {:?}, what: {:?}", _origin, what ); // Check we handle this asset. diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 42522c64d8a4da77cc676e6a338d9f019ef707a5..e2af8187136e8eba2b42d7d4a667a69830556032 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -117,7 +117,7 @@ mod process_xcm_message; pub use process_xcm_message::ProcessXcmMessage; mod routing; -pub use routing::{WithTopicSource, WithUniqueTopic}; +pub use routing::{EnsureDelivery, WithTopicSource, WithUniqueTopic}; mod transactional; pub use transactional::FrameTransactionalProcessor; diff --git a/polkadot/xcm/xcm-builder/src/routing.rs b/polkadot/xcm/xcm-builder/src/routing.rs index 9c0302baee06b81ec662bc3a0e25eb9308fc0a3a..529ef80c15ff11d3c0d2629aeb4fa9506cb37a28 100644 --- a/polkadot/xcm/xcm-builder/src/routing.rs +++ b/polkadot/xcm/xcm-builder/src/routing.rs @@ -20,6 +20,7 @@ use frame_system::unique; use parity_scale_codec::Encode; use sp_std::{marker::PhantomData, result::Result}; use xcm::prelude::*; +use xcm_executor::{traits::FeeReason, FeesMode}; /// Wrapper router which, if the message does not already end with a `SetTopic` instruction, /// appends one to the message filled with a universally unique ID. This ID is returned from a @@ -104,3 +105,37 @@ impl SendXcm for WithTopicSource (Option, Option); +} + +/// Tuple implementation for `EnsureDelivery`. +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl EnsureDelivery for Tuple { + fn ensure_successful_delivery( + origin_ref: &Location, + dest: &Location, + fee_reason: FeeReason, + ) -> (Option, Option) { + for_tuples!( #( + // If the implementation returns something, we're done; if not, let others try. + match Tuple::ensure_successful_delivery(origin_ref, dest, fee_reason.clone()) { + r @ (Some(_), Some(_)) | r @ (Some(_), None) | r @ (None, Some(_)) => return r, + (None, None) => (), + } + )* ); + // doing nothing + (None, None) + } +} diff --git a/polkadot/xcm/xcm-builder/tests/mock/mod.rs b/polkadot/xcm/xcm-builder/tests/mock/mod.rs index 34508d7622c5f8a3e703cefa833ea6dafd900e56..06cedb9c35775898e264a58e175ebe81e06d10a4 100644 --- a/polkadot/xcm/xcm-builder/tests/mock/mod.rs +++ b/polkadot/xcm/xcm-builder/tests/mock/mod.rs @@ -32,14 +32,12 @@ use xcm_executor::XcmExecutor; use staging_xcm_builder as xcm_builder; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, - FixedRateOfFungible, FixedWeightBounds, IsChildSystemParachain, IsConcrete, MintLocation, - RespectSuspension, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, - TakeWeightCredit, + FixedRateOfFungible, FixedWeightBounds, FungibleAdapter, IsChildSystemParachain, IsConcrete, + MintLocation, RespectSuspension, SignedAccountId32AsNative, SignedToAccountId32, + SovereignSignedViaLocation, TakeWeightCredit, }; pub type AccountId = AccountId32; @@ -146,14 +144,8 @@ parameter_types! { pub type SovereignAccountOf = (ChildParachainConvertsVia, AccountId32Aliases); -#[allow(deprecated)] -pub type LocalCurrencyAdapter = XcmCurrencyAdapter< - Balances, - IsConcrete, - SovereignAccountOf, - AccountId, - CheckAccount, ->; +pub type LocalCurrencyAdapter = + FungibleAdapter, SovereignAccountOf, AccountId, CheckAccount>; pub type LocalAssetTransactor = (LocalCurrencyAdapter,); diff --git a/polkadot/xcm/xcm-executor/Cargo.toml b/polkadot/xcm/xcm-executor/Cargo.toml index 7ce4a1cc171ded3eba5ab6711d9dc307a0a2cc05..71bd58073db6d7247cf5048d4a684aa19ea79bfd 100644 --- a/polkadot/xcm/xcm-executor/Cargo.toml +++ b/polkadot/xcm/xcm-executor/Cargo.toml @@ -22,7 +22,7 @@ sp-core = { path = "../../../substrate/primitives/core", default-features = fals sp-runtime = { path = "../../../substrate/primitives/runtime", default-features = false } sp-weights = { path = "../../../substrate/primitives/weights", default-features = false } frame-support = { path = "../../../substrate/frame/support", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } frame-benchmarking = { path = "../../../substrate/frame/benchmarking", default-features = false, optional = true } [features] diff --git a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml index cafe12dc587f883a31ac3539aced38e8de29a89e..1e572e6210a27a7469f827bbe5d076c01c84f032 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml +++ b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml @@ -20,6 +20,7 @@ pallet-xcm = { path = "../../pallet-xcm" } polkadot-test-client = { path = "../../../node/test/client" } polkadot-test-runtime = { path = "../../../runtime/test-runtime" } polkadot-test-service = { path = "../../../node/test/service" } +polkadot-service = { path = "../../../node/service" } sp-consensus = { path = "../../../../substrate/primitives/consensus/common" } sp-keyring = { path = "../../../../substrate/primitives/keyring" } sp-runtime = { path = "../../../../substrate/primitives/runtime", default-features = false } @@ -27,6 +28,7 @@ sp-state-machine = { path = "../../../../substrate/primitives/state-machine" } xcm = { package = "staging-xcm", path = "../..", default-features = false } xcm-executor = { package = "staging-xcm-executor", path = ".." } sp-tracing = { path = "../../../../substrate/primitives/tracing" } +sp-core = { path = "../../../../substrate/primitives/core" } [features] default = ["std"] diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index 79d6cb1c411b12da2a9f6b81a01b93a44c97ebe9..da7fc0d97825f24e1eea91e441cecef11a136e5f 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -19,12 +19,14 @@ use codec::Encode; use frame_support::{dispatch::GetDispatchInfo, weights::Weight}; +use polkadot_service::chain_spec::get_account_id_from_seed; use polkadot_test_client::{ BlockBuilderExt, ClientBlockImportExt, DefaultTestClientBuilderExt, InitPolkadotBlockBuilder, TestClientBuilder, TestClientBuilderExt, }; use polkadot_test_runtime::{pallet_test_notifier, xcm_config::XcmConfig}; use polkadot_test_service::construct_extrinsic; +use sp_core::sr25519; use sp_runtime::traits::Block; use sp_state_machine::InspectState; use xcm::{latest::prelude::*, VersionedResponse, VersionedXcm}; @@ -323,3 +325,84 @@ fn query_response_elicits_handler() { ))); }); } + +/// Simulates a cross-chain message from Parachain to Parachain through Relay Chain +/// that deposits assets into the reserve of the destination. +/// Regression test for `DepostiReserveAsset` changes in +/// +#[test] +fn deposit_reserve_asset_works_for_any_xcm_sender() { + sp_tracing::try_init_simple(); + let mut client = TestClientBuilder::new().build(); + + // Init values for the simulated origin Parachain + let amount_to_send: u128 = 1_000_000_000_000; + let assets: Assets = (Parent, amount_to_send).into(); + let fee_asset_item = 0; + let max_assets = assets.len() as u32; + let fees = assets.get(fee_asset_item as usize).unwrap().clone(); + let weight_limit = Unlimited; + let reserve = Location::parent(); + let dest = Location::new(1, [Parachain(2000)]); + let beneficiary_id = get_account_id_from_seed::("Alice"); + let beneficiary = Location::new(0, [AccountId32 { network: None, id: beneficiary_id.into() }]); + + // spends up to half of fees for execution on reserve and other half for execution on + // destination + let fee1 = amount_to_send.saturating_div(2); + let fee2 = amount_to_send.saturating_sub(fee1); + let fees_half_1 = Asset::from((fees.id.clone(), Fungible(fee1))); + let fees_half_2 = Asset::from((fees.id.clone(), Fungible(fee2))); + + let reserve_context = ::UniversalLocation::get(); + // identifies fee item as seen by `reserve` - to be used at reserve chain + let reserve_fees = fees_half_1.reanchored(&reserve, &reserve_context).unwrap(); + // identifies fee item as seen by `dest` - to be used at destination chain + let dest_fees = fees_half_2.reanchored(&dest, &reserve_context).unwrap(); + // identifies assets as seen by `reserve` - to be used at reserve chain + let assets_reanchored = assets.reanchored(&reserve, &reserve_context).unwrap(); + // identifies `dest` as seen by `reserve` + let dest = dest.reanchored(&reserve, &reserve_context).unwrap(); + // xcm to be executed at dest + let xcm_on_dest = Xcm(vec![ + BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }, + DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }, + ]); + // xcm to be executed at reserve + let msg = Xcm(vec![ + WithdrawAsset(assets_reanchored), + ClearOrigin, + BuyExecution { fees: reserve_fees, weight_limit }, + DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest }, + ]); + + let mut block_builder = client.init_polkadot_block_builder(); + + // Simulate execution of an incoming XCM message at the reserve chain + let execute = construct_extrinsic( + &client, + polkadot_test_runtime::RuntimeCall::Xcm(pallet_xcm::Call::execute { + message: Box::new(VersionedXcm::from(msg)), + max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), + }), + sp_keyring::Sr25519Keyring::Alice, + 0, + ); + + block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); + + let block = block_builder.build().expect("Finalizes the block").block; + let block_hash = block.hash(); + + futures::executor::block_on(client.import(sp_consensus::BlockOrigin::Own, block)) + .expect("imports the block"); + + client.state_at(block_hash).expect("state should exist").inspect_state(|| { + assert!(polkadot_test_runtime::System::events().iter().any(|r| matches!( + r.event, + polkadot_test_runtime::RuntimeEvent::Xcm(pallet_xcm::Event::Attempted { + outcome: Outcome::Complete { .. } + }), + ))); + }); +} diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index b26779f3ae9da206d76823027dc193dc771cc215..c61e1e1d15bcd8fd78ffe47c415abeaf8c1cb4cd 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -826,9 +826,9 @@ impl XcmExecutor { // be weighed let to_weigh = self.holding.saturating_take(assets.clone()); self.holding.subsume_assets(to_weigh.clone()); - + let to_weigh_reanchored = Self::reanchored(to_weigh, &dest, None); let mut message_to_weigh = - vec![ReserveAssetDeposited(to_weigh.into()), ClearOrigin]; + vec![ReserveAssetDeposited(to_weigh_reanchored), ClearOrigin]; message_to_weigh.extend(xcm.0.clone().into_iter()); let (_, fee) = validate_send::(dest.clone(), Xcm(message_to_weigh))?; diff --git a/polkadot/xcm/xcm-simulator/example/Cargo.toml b/polkadot/xcm/xcm-simulator/example/Cargo.toml index 9cb5b6b7eeb92e7cf6095d2e86ed9b05185165c4..af471df60aba4dbc4d03692e7bfb84d99eb16420 100644 --- a/polkadot/xcm/xcm-simulator/example/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/example/Cargo.toml @@ -12,7 +12,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } scale-info = { version = "2.10.0", features = ["derive"] } -log = { version = "0.4.14", default-features = false } +log = { workspace = true } frame-system = { path = "../../../../substrate/frame/system" } frame-support = { path = "../../../../substrate/frame/support" } diff --git a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml index 13b6e7b8652fbbb178071f429b286b6c5b8d64ac..30644dc0e0a53fe485261f9d0e52533ba0641603 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml @@ -18,6 +18,8 @@ scale-info = { version = "2.10.0", features = ["derive"] } frame-system = { path = "../../../../substrate/frame/system" } frame-support = { path = "../../../../substrate/frame/support" } +frame-executive = { path = "../../../../substrate/frame/executive" } +frame-try-runtime = { path = "../../../../substrate/frame/try-runtime" } pallet-balances = { path = "../../../../substrate/frame/balances" } pallet-message-queue = { path = "../../../../substrate/frame/message-queue" } sp-std = { path = "../../../../substrate/primitives/std" } @@ -35,6 +37,17 @@ polkadot-runtime-parachains = { path = "../../../runtime/parachains" } polkadot-parachain-primitives = { path = "../../../parachain" } [features] +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-balances/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-xcm/try-runtime", + "polkadot-runtime-parachains/try-runtime", + "sp-runtime/try-runtime", +] runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/polkadot/xcm/xcm-simulator/fuzzer/README.md b/polkadot/xcm/xcm-simulator/fuzzer/README.md index 0b3fdd8ec776d5e78d37aa5b57e6c0762af21320..9c15ee881c1b6f5f9a1c30215a16e74abdda2b62 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/README.md +++ b/polkadot/xcm/xcm-simulator/fuzzer/README.md @@ -14,7 +14,7 @@ cargo install honggfuzz In this directory, run this command: ``` -cargo hfuzz run xcm-fuzzer +HFUZZ_BUILD_ARGS="--features=try-runtime" cargo hfuzz run xcm-fuzzer ``` ## Run a single input @@ -22,7 +22,7 @@ cargo hfuzz run xcm-fuzzer In this directory, run this command: ``` -cargo hfuzz run-debug xcm-fuzzer hfuzz_workspace/xcm-fuzzer/fuzzer_input_file +cargo run --features=try-runtime -- hfuzz_workspace/xcm-fuzzer/fuzzer_input_file ``` ## Generate coverage @@ -31,7 +31,7 @@ In this directory, run these four commands: ``` RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" \ -CARGO_INCREMENTAL=0 SKIP_WASM_BUILD=1 CARGO_HOME=./cargo cargo build +CARGO_INCREMENTAL=0 SKIP_WASM_BUILD=1 CARGO_HOME=./cargo cargo build --features=try-runtime ../../../target/debug/xcm-fuzzer hfuzz_workspace/xcm-fuzzer/input/ zip -0 ccov.zip `find ../../../target/ \( -name "*.gc*" -o -name "test-*.gc*" \) -print` grcov ccov.zip -s ../../../ -t html --llvm --branch --ignore-not-existing -o ./coverage diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs index 7026d5467c8b9049eaa86f94e9cd927321d10d68..adf6cacd278b9f25a02f9199fbdabc0af3434414 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs @@ -23,7 +23,9 @@ use polkadot_parachain_primitives::primitives::Id as ParaId; use sp_runtime::{traits::AccountIdConversion, BuildStorage}; use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt}; -use frame_support::assert_ok; +#[cfg(feature = "try-runtime")] +use frame_support::traits::{TryState, TryStateSelect::All}; +use frame_support::{assert_ok, traits::IntegrityTest}; use xcm::{latest::prelude::*, MAX_XCM_DECODE_DEPTH}; use arbitrary::{Arbitrary, Error, Unstructured}; @@ -98,7 +100,7 @@ impl<'a> Arbitrary<'a> for XcmMessage { if let Ok(message) = DecodeLimit::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut encoded_message) { - return Ok(XcmMessage { source, destination, message }) + return Ok(XcmMessage { source, destination, message }); } Err(Error::IncorrectFormat) } @@ -148,6 +150,21 @@ pub fn relay_ext() -> sp_io::TestExternalities { pub type RelayChainPalletXcm = pallet_xcm::Pallet; pub type ParachainPalletXcm = pallet_xcm::Pallet; +// We check XCM messages recursively for blocklisted messages +fn recursively_matches_blocklisted_messages(message: &Instruction<()>) -> bool { + match message { + DepositReserveAsset { xcm, .. } | + ExportMessage { xcm, .. } | + InitiateReserveWithdraw { xcm, .. } | + InitiateTeleport { xcm, .. } | + TransferReserveAsset { xcm, .. } | + SetErrorHandler(xcm) | + SetAppendix(xcm) => xcm.iter().any(recursively_matches_blocklisted_messages), + // The blocklisted message is the Transact instruction. + m => matches!(m, Transact { .. }), + } +} + fn run_input(xcm_messages: [XcmMessage; 5]) { MockNet::reset(); @@ -155,6 +172,11 @@ fn run_input(xcm_messages: [XcmMessage; 5]) { println!(); for xcm_message in xcm_messages { + if xcm_message.message.iter().any(recursively_matches_blocklisted_messages) { + println!(" skipping message\n"); + continue; + } + if xcm_message.source % 4 == 0 { // We get the destination for the message let parachain_id = (xcm_message.destination % 3) + 1; @@ -197,8 +219,22 @@ fn run_input(xcm_messages: [XcmMessage; 5]) { } #[cfg(not(fuzzing))] println!(); + // We run integrity tests and try_runtime invariants + [ParaA::execute_with, ParaB::execute_with, ParaC::execute_with].iter().for_each( + |execute_with| { + execute_with(|| { + #[cfg(feature = "try-runtime")] + parachain::AllPalletsWithSystem::try_state(Default::default(), All).unwrap(); + parachain::AllPalletsWithSystem::integrity_test(); + }); + }, + ); + Relay::execute_with(|| { + #[cfg(feature = "try-runtime")] + relay_chain::AllPalletsWithSystem::try_state(Default::default(), All).unwrap(); + relay_chain::AllPalletsWithSystem::integrity_test(); + }); } - Relay::execute_with(|| {}); } fn main() { diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs index d8327c9b401d0ba67ec6cccb12e16ea70ee1ba3e..a20390b64f946db2633ae2538b4a869b53fa338a 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/parachain.rs @@ -24,10 +24,11 @@ use frame_support::{ }; use frame_system::EnsureRoot; -use sp_core::{ConstU32, H256}; +use sp_core::ConstU32; use sp_runtime::{ - traits::{Hash, IdentityLookup}, - AccountId32, + generic, + traits::{AccountIdLookup, BlakeTwo256, Hash, IdentifyAccount, Verify}, + MultiAddress, MultiSignature, }; use sp_std::prelude::*; @@ -37,48 +38,37 @@ use polkadot_parachain_primitives::primitives::{ DmpMessageHandler, Id as ParaId, Sibling, XcmpMessageFormat, XcmpMessageHandler, }; use xcm::{latest::prelude::*, VersionedXcm}; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, EnsureXcmOrigin, FixedRateOfFungible, - FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, NativeAsset, ParentIsPreset, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, + ParentIsPreset, SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; -pub type AccountId = AccountId32; +pub type SignedExtra = (frame_system::CheckNonZeroSender,); + +pub type BlockNumber = u64; +pub type Address = MultiAddress; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; pub type Balance = u128; parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: u32 = 250; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; - type Lookup = IdentityLookup; + type Lookup = AccountIdLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { @@ -133,9 +123,8 @@ parameter_types! { pub const MaxAssetsIntoHolding: u32 = 64; } -#[allow(deprecated)] pub type LocalAssetTransactor = - XcmCurrencyAdapter, LocationToAccountId, AccountId, ()>; + FungibleAdapter, LocationToAccountId, AccountId, ()>; pub type XcmRouter = super::ParachainXcmRouter; pub type Barrier = AllowUnpaidExecutionFrom; @@ -356,8 +345,6 @@ impl pallet_xcm::Config for Runtime { type AdminOrigin = EnsureRoot; } -type Block = frame_system::mocking::MockBlock; - construct_runtime!( pub enum Runtime { diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs index 7e42f558dd6e89cbac2e5f378e16d75fd07ffa78..5bf65fa9f9ac407ead13a008919aa2796867ccdd 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/relay_chain.rs @@ -23,8 +23,12 @@ use frame_support::{ }; use frame_system::EnsureRoot; -use sp_core::{ConstU32, H256}; -use sp_runtime::{traits::IdentityLookup, AccountId32}; +use sp_core::ConstU32; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentifyAccount, Verify}, + MultiAddress, MultiSignature, +}; use polkadot_parachain_primitives::primitives::Id as ParaId; use polkadot_runtime_parachains::{ @@ -33,48 +37,37 @@ use polkadot_runtime_parachains::{ origin, shared, }; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowUnpaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, FixedRateOfFungible, - FixedWeightBounds, FrameTransactionalProcessor, IsConcrete, SignedAccountId32AsNative, - SignedToAccountId32, SovereignSignedViaLocation, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, IsConcrete, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, }; use xcm_executor::{Config, XcmExecutor}; -pub type AccountId = AccountId32; +pub type SignedExtra = (frame_system::CheckNonZeroSender,); + +pub type BlockNumber = u64; +pub type Address = MultiAddress; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; pub type Balance = u128; parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: u32 = 250; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Runtime { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; - type Lookup = IdentityLookup; + type Lookup = sp_runtime::traits::AccountIdLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = BlockHashCount; - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type DbWeight = (); - type BaseCallFilter = Everything; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { @@ -117,9 +110,8 @@ parameter_types! { pub type SovereignAccountOf = (ChildParachainConvertsVia, AccountId32Aliases); -#[allow(deprecated)] pub type LocalAssetTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + FungibleAdapter, SovereignAccountOf, AccountId, ()>; type LocalOriginConverter = ( SovereignSignedViaLocation, @@ -202,8 +194,6 @@ parameter_types! { impl origin::Config for Runtime {} -type Block = frame_system::mocking::MockBlock; - parameter_types! { /// Amount of weight that can be spent per block to service messages. pub MessageQueueServiceWeight: Weight = Weight::from_parts(1_000_000_000, 1_000_000); diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml new file mode 100644 index 0000000000000000000000000000000000000000..5a6832b149be21a48ca17fe6e84f75cfc3324ed0 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml @@ -0,0 +1,51 @@ +[settings] +timeout = 1000 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + needed_approvals = 4 + relay_vrf_modulo_samples = 6 + scheduling_lookahead = 2 + group_rotation_frequency = 4 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] + max_candidate_depth = 3 + allowed_ancestry_len = 2 + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.node_groups]] + name = "alice" + args = [ "-lparachain=debug" ] + count = 12 + +[[parachains]] +id = 2000 +addToGenesis = true +genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=1" + + [parachains.collator] + name = "collator01" + image = "{{COL_IMAGE}}" + command = "undying-collator" + args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=1", "--parachain-id=2000"] + +[[parachains]] +id = 2001 +cumulus_based = true + + [parachains.collator] + name = "collator02" + image = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = ["-lparachain=debug"] + +[types.Header] +number = "u64" +parent_hash = "Hash" +post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..0d01af82833e36afd3c38b2e00e9d604ace46797 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl @@ -0,0 +1,20 @@ +Description: Test we are producing blocks at 6 seconds clip +Network: ./0011-async-backing-6-seconds-rate.toml +Creds: config + +# Check authority status. +alice: reports node_roles is 4 + +# Ensure parachains are registered. +alice: parachain 2000 is registered within 60 seconds +alice: parachain 2001 is registered within 60 seconds + +# Ensure parachains made progress. +alice: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds + +# This parachains should produce blocks at 6s clip, let's assume an 8s rate, allowing for +# some slots to be missed on slower machines +alice: parachain 2000 block height is at least 30 within 240 seconds +# This should already have produced the needed blocks +alice: parachain 2001 block height is at least 30 within 6 seconds + diff --git a/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml new file mode 100644 index 0000000000000000000000000000000000000000..0dfd814e10a5ecd4437f469b9869dff5c27047f3 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.toml @@ -0,0 +1,38 @@ +[settings] +timeout = 1000 +bootnode = true + +[relaychain.genesis.runtimeGenesis.patch.configuration.config] + max_validators_per_core = 2 + needed_approvals = 4 + coretime_cores = 2 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +default_command = "polkadot" + +[relaychain.default_resources] +limits = { memory = "4G", cpu = "2" } +requests = { memory = "2G", cpu = "1" } + + [[relaychain.nodes]] + name = "alice" + validator = "true" + + [[relaychain.node_groups]] + name = "validator" + count = 3 + args = [ "-lparachain=debug,runtime=debug"] + +[[parachains]] +id = 2000 +default_command = "polkadot-parachain" +add_to_genesis = false +register_para = true +onboard_as_parachain = false + + [parachains.collator] + name = "collator2000" + command = "polkadot-parachain" + args = [ "-lparachain=debug" ] diff --git a/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl new file mode 100644 index 0000000000000000000000000000000000000000..a7193c9282b961f00134c2336695d4f2737a5b91 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-elastic-scaling-mvp.zndsl @@ -0,0 +1,28 @@ +Description: Test that a paraid acquiring multiple cores does not brick itself if ElasticScalingMVP feature is enabled +Network: ./0012-elastic-scaling-mvp.toml +Creds: config + +# Check authority status. +validator: reports node_roles is 4 + +validator: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds + +# Ensure parachain was able to make progress. +validator: parachain 2000 block height is at least 10 within 200 seconds + +# Register the second core assigned to this parachain. +alice: js-script ./0012-register-para.js return is 0 within 600 seconds + +validator: reports substrate_block_height{status="finalized"} is at least 35 within 100 seconds + +# Parachain will now be stalled +validator: parachain 2000 block height is lower than 20 within 300 seconds + +# Enable the ElasticScalingMVP node feature. +alice: js-script ./0012-enable-node-feature.js with "1" return is 0 within 600 seconds + +# Wait two sessions for the config to be updated. +sleep 120 seconds + +# Ensure parachain is now making progress. +validator: parachain 2000 block height is at least 30 within 200 seconds diff --git a/polkadot/zombienet_tests/functional/0012-enable-node-feature.js b/polkadot/zombienet_tests/functional/0012-enable-node-feature.js new file mode 100644 index 0000000000000000000000000000000000000000..4822e1f664478a93579c58e82db0f694b7008ee8 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-enable-node-feature.js @@ -0,0 +1,37 @@ +async function run(nodeName, networkInfo, index) { + const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + await zombie.util.cryptoWaitReady(); + + // account to submit tx + const keyring = new zombie.Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice"); + + await new Promise(async (resolve, reject) => { + const unsub = await api.tx.sudo + .sudo(api.tx.configuration.setNodeFeature(Number(index), true)) + .signAndSend(alice, ({ status, isError }) => { + if (status.isInBlock) { + console.log( + `Transaction included at blockhash ${status.asInBlock}`, + ); + } else if (status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${status.asFinalized}`, + ); + unsub(); + return resolve(); + } else if (isError) { + console.log(`Transaction error`); + reject(`Transaction error`); + } + }); + }); + + + + return 0; +} + +module.exports = { run }; diff --git a/polkadot/zombienet_tests/functional/0012-register-para.js b/polkadot/zombienet_tests/functional/0012-register-para.js new file mode 100644 index 0000000000000000000000000000000000000000..25c7e4f5ffddcf087edab4df37e696af92fe6d3f --- /dev/null +++ b/polkadot/zombienet_tests/functional/0012-register-para.js @@ -0,0 +1,37 @@ +async function run(nodeName, networkInfo, _jsArgs) { + const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + await zombie.util.cryptoWaitReady(); + + // account to submit tx + const keyring = new zombie.Keyring({ type: "sr25519" }); + const alice = keyring.addFromUri("//Alice"); + + await new Promise(async (resolve, reject) => { + const unsub = await api.tx.sudo + .sudo(api.tx.coretime.assignCore(0, 35, [[{ task: 2000 }, 57600]], null)) + .signAndSend(alice, ({ status, isError }) => { + if (status.isInBlock) { + console.log( + `Transaction included at blockhash ${status.asInBlock}`, + ); + } else if (status.isFinalized) { + console.log( + `Transaction finalized at blockHash ${status.asFinalized}`, + ); + unsub(); + return resolve(); + } else if (isError) { + console.log(`Transaction error`); + reject(`Transaction error`); + } + }); + }); + + + + return 0; +} + +module.exports = { run }; diff --git a/polkadot/zombienet_tests/misc/0001-paritydb.zndsl b/polkadot/zombienet_tests/misc/0001-paritydb.zndsl index 4a22311de764c1936be2f5812ffb32a7471b5f8e..e0260cb9fdde18882bd9e1cb1ec88a12cdb7a3b9 100644 --- a/polkadot/zombienet_tests/misc/0001-paritydb.zndsl +++ b/polkadot/zombienet_tests/misc/0001-paritydb.zndsl @@ -31,28 +31,28 @@ validator-0: parachain 2008 is registered validator-0: parachain 2009 is registered # Ensure parachains made some progress. -validator-0: parachain 2000 block height is at least 3 within 30 seconds -validator-0: parachain 2001 block height is at least 3 within 30 seconds -validator-0: parachain 2002 block height is at least 3 within 30 seconds -validator-0: parachain 2003 block height is at least 3 within 30 seconds -validator-0: parachain 2004 block height is at least 3 within 30 seconds -validator-0: parachain 2005 block height is at least 3 within 30 seconds -validator-0: parachain 2006 block height is at least 3 within 30 seconds -validator-0: parachain 2007 block height is at least 3 within 30 seconds -validator-0: parachain 2008 block height is at least 3 within 30 seconds -validator-0: parachain 2009 block height is at least 3 within 30 seconds +validator-0: parachain 2000 block height is at least 3 within 60 seconds +validator-0: parachain 2001 block height is at least 3 within 60 seconds +validator-0: parachain 2002 block height is at least 3 within 60 seconds +validator-0: parachain 2003 block height is at least 3 within 60 seconds +validator-0: parachain 2004 block height is at least 3 within 60 seconds +validator-0: parachain 2005 block height is at least 3 within 60 seconds +validator-0: parachain 2006 block height is at least 3 within 60 seconds +validator-0: parachain 2007 block height is at least 3 within 60 seconds +validator-0: parachain 2008 block height is at least 3 within 60 seconds +validator-0: parachain 2009 block height is at least 3 within 60 seconds # Check lag - approval -validator-0: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-1: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-2: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-3: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-4: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-5: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-6: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-7: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-8: reports polkadot_parachain_approval_checking_finality_lag is 0 -validator-9: reports polkadot_parachain_approval_checking_finality_lag is 0 +validator-0: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-1: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-2: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-3: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-4: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-5: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-6: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-7: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-8: reports polkadot_parachain_approval_checking_finality_lag <= 1 +validator-9: reports polkadot_parachain_approval_checking_finality_lag <= 1 # Check lag - dispute conclusion validator-0: reports polkadot_parachain_candidate_disputes_total is 0 diff --git a/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl b/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl index db0a60ac1df617e5c89dc6a1385c4c106c1ead05..5fe1b2ad2f1a7589a28f8b1b8da05d6f35d3d59b 100644 --- a/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl +++ b/polkadot/zombienet_tests/misc/0002-upgrade-node.zndsl @@ -10,9 +10,8 @@ dave: parachain 2001 block height is at least 10 within 200 seconds # POLKADOT_PR_ARTIFACTS_URL=https://gitlab.parity.io/parity/mirrors/polkadot/-/jobs/1842869/artifacts/raw/artifacts # with the version of polkadot you want to download. -# avg 30s in our infra -alice: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 60 seconds -bob: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 60 seconds +alice: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 240 seconds +bob: run ./0002-download-polkadot-from-pr.sh with "{{POLKADOT_PR_ARTIFACTS_URL}}" within 240 seconds # update the cmd to add the flag '--insecure-validator-i-know-what-i-do' # once the base image include the version with this flag we can remove this logic. alice: run ./0002-update-cmd.sh within 60 seconds diff --git a/polkadot/zombienet_tests/smoke/0004-configure-broker.js b/polkadot/zombienet_tests/smoke/0004-configure-broker.js index 889861f5c52e3d8c6a56ca44bc0b2d378d153011..52a32b8a7c802e33ed41acceb155024be2da7ca0 100644 --- a/polkadot/zombienet_tests/smoke/0004-configure-broker.js +++ b/polkadot/zombienet_tests/smoke/0004-configure-broker.js @@ -54,9 +54,11 @@ async function run(nodeName, networkInfo, _jsArgs) { unsub(); return resolve(); } else if (result.isError) { - console.log(`Transaction Error`); + // Probably happens because of: https://github.com/paritytech/polkadot-sdk/issues/1202. + console.log(`Transaction error`); + // We ignore the error because it is very likely misleading, because of the issue mentioned above. unsub(); - return reject(); + return resolve(); } }); }); diff --git a/prdoc/1.4.0/pr_1246.prdoc b/prdoc/1.4.0/pr_1246.prdoc index a4d270c45cb5915760ddfbd60e5e4b3b7c08cd4a..3b5c2017f22acfba25471595c50015ae14c3e5cd 100644 --- a/prdoc/1.4.0/pr_1246.prdoc +++ b/prdoc/1.4.0/pr_1246.prdoc @@ -11,7 +11,7 @@ migrations: description: "Messages from the DMP dispatch queue will be moved over to the MQ pallet via `on_initialize`. This happens over multiple blocks and emits a `Completed` event at the end. The pallet can be un-deployed and deleted afterwards. Note that the migration reverses the order of messages, which should be acceptable as a one-off." crates: - - name: cumulus_pallet_xcmp_queue + - name: cumulus-pallet-xcmp-queue note: Pallet config must be altered according to the MR description. host_functions: [] diff --git a/prdoc/1.6.0/pr_2689.prdoc b/prdoc/1.6.0/pr_2689.prdoc index 847c3e8026cef9dfdf63f044a1b773be85b12921..5d3081e3a4ce71d872c8eb8a96fda5b2e273b684 100644 --- a/prdoc/1.6.0/pr_2689.prdoc +++ b/prdoc/1.6.0/pr_2689.prdoc @@ -1,7 +1,7 @@ # Schema: Parity PR Documentation Schema (prdoc) # See doc at https://github.com/paritytech/prdoc -title: BEEFY: Support compatibility with Warp Sync - Allow Warp Sync for Validators +title: "BEEFY: Support compatibility with Warp Sync - Allow Warp Sync for Validators" doc: - audience: Node Operator diff --git a/prdoc/1.6.0/pr_2771.prdoc b/prdoc/1.6.0/pr_2771.prdoc index 1b49162e4392ba1ad1a77d61e5b2289474b0ffbe..50fb99556ecddf39adbcece77d9b83e5d98651b4 100644 --- a/prdoc/1.6.0/pr_2771.prdoc +++ b/prdoc/1.6.0/pr_2771.prdoc @@ -6,4 +6,4 @@ doc: Enable better req-response protocol versioning, by allowing for fallback requests on different protocols. crates: - - name: sc_network + - name: sc-network diff --git a/prdoc/pr_1222.prdoc b/prdoc/1.7.0/pr_1222.prdoc similarity index 100% rename from prdoc/pr_1222.prdoc rename to prdoc/1.7.0/pr_1222.prdoc diff --git a/prdoc/pr_1230.prdoc b/prdoc/1.7.0/pr_1230.prdoc similarity index 100% rename from prdoc/pr_1230.prdoc rename to prdoc/1.7.0/pr_1230.prdoc diff --git a/prdoc/pr_1296.prdoc b/prdoc/1.7.0/pr_1296.prdoc similarity index 100% rename from prdoc/pr_1296.prdoc rename to prdoc/1.7.0/pr_1296.prdoc diff --git a/prdoc/pr_1313.prdoc b/prdoc/1.7.0/pr_1313.prdoc similarity index 100% rename from prdoc/pr_1313.prdoc rename to prdoc/1.7.0/pr_1313.prdoc diff --git a/prdoc/pr_1845.prdoc b/prdoc/1.7.0/pr_1845.prdoc similarity index 100% rename from prdoc/pr_1845.prdoc rename to prdoc/1.7.0/pr_1845.prdoc diff --git a/prdoc/pr_1871.prdoc b/prdoc/1.7.0/pr_1871.prdoc similarity index 100% rename from prdoc/pr_1871.prdoc rename to prdoc/1.7.0/pr_1871.prdoc diff --git a/prdoc/pr_2125.prdoc b/prdoc/1.7.0/pr_2125.prdoc similarity index 100% rename from prdoc/pr_2125.prdoc rename to prdoc/1.7.0/pr_2125.prdoc diff --git a/prdoc/pr_2467.prdoc b/prdoc/1.7.0/pr_2467.prdoc similarity index 100% rename from prdoc/pr_2467.prdoc rename to prdoc/1.7.0/pr_2467.prdoc diff --git a/prdoc/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc b/prdoc/1.7.0/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc similarity index 100% rename from prdoc/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc rename to prdoc/1.7.0/pr_2477-use-clone-instead-of-fork-on-pvf.prdoc diff --git a/prdoc/pr_2587.prdoc b/prdoc/1.7.0/pr_2587.prdoc similarity index 100% rename from prdoc/pr_2587.prdoc rename to prdoc/1.7.0/pr_2587.prdoc diff --git a/prdoc/pr_2657.prdoc b/prdoc/1.7.0/pr_2657.prdoc similarity index 100% rename from prdoc/pr_2657.prdoc rename to prdoc/1.7.0/pr_2657.prdoc diff --git a/prdoc/pr_2796.prdoc b/prdoc/1.7.0/pr_2796.prdoc similarity index 100% rename from prdoc/pr_2796.prdoc rename to prdoc/1.7.0/pr_2796.prdoc diff --git a/prdoc/pr_2826.prdoc b/prdoc/1.7.0/pr_2826.prdoc similarity index 100% rename from prdoc/pr_2826.prdoc rename to prdoc/1.7.0/pr_2826.prdoc diff --git a/prdoc/pr_2889.prdoc b/prdoc/1.7.0/pr_2889.prdoc similarity index 100% rename from prdoc/pr_2889.prdoc rename to prdoc/1.7.0/pr_2889.prdoc diff --git a/prdoc/pr_2920.prdoc b/prdoc/1.7.0/pr_2920.prdoc similarity index 100% rename from prdoc/pr_2920.prdoc rename to prdoc/1.7.0/pr_2920.prdoc diff --git a/prdoc/pr_2924.prdoc b/prdoc/1.7.0/pr_2924.prdoc similarity index 100% rename from prdoc/pr_2924.prdoc rename to prdoc/1.7.0/pr_2924.prdoc diff --git a/prdoc/pr_2942.prdoc b/prdoc/1.7.0/pr_2942.prdoc similarity index 100% rename from prdoc/pr_2942.prdoc rename to prdoc/1.7.0/pr_2942.prdoc diff --git a/prdoc/pr_2970.prdoc b/prdoc/1.7.0/pr_2970.prdoc similarity index 100% rename from prdoc/pr_2970.prdoc rename to prdoc/1.7.0/pr_2970.prdoc diff --git a/prdoc/pr_3001.prdoc b/prdoc/1.7.0/pr_3001.prdoc similarity index 100% rename from prdoc/pr_3001.prdoc rename to prdoc/1.7.0/pr_3001.prdoc diff --git a/prdoc/pr_3009.prdoc b/prdoc/1.7.0/pr_3009.prdoc similarity index 100% rename from prdoc/pr_3009.prdoc rename to prdoc/1.7.0/pr_3009.prdoc diff --git a/prdoc/pr_3020.prdoc b/prdoc/1.7.0/pr_3020.prdoc similarity index 100% rename from prdoc/pr_3020.prdoc rename to prdoc/1.7.0/pr_3020.prdoc diff --git a/prdoc/pr_3029.prdoc b/prdoc/1.7.0/pr_3029.prdoc similarity index 100% rename from prdoc/pr_3029.prdoc rename to prdoc/1.7.0/pr_3029.prdoc diff --git a/prdoc/pr_3040.prdoc b/prdoc/1.7.0/pr_3040.prdoc similarity index 100% rename from prdoc/pr_3040.prdoc rename to prdoc/1.7.0/pr_3040.prdoc diff --git a/prdoc/pr_3057.prdoc b/prdoc/1.7.0/pr_3057.prdoc similarity index 100% rename from prdoc/pr_3057.prdoc rename to prdoc/1.7.0/pr_3057.prdoc diff --git a/prdoc/pr_3061.prdoc b/prdoc/1.7.0/pr_3061.prdoc similarity index 100% rename from prdoc/pr_3061.prdoc rename to prdoc/1.7.0/pr_3061.prdoc diff --git a/prdoc/pr_3077.prdoc b/prdoc/1.7.0/pr_3077.prdoc similarity index 100% rename from prdoc/pr_3077.prdoc rename to prdoc/1.7.0/pr_3077.prdoc diff --git a/prdoc/pr_3108.prdoc b/prdoc/1.7.0/pr_3108.prdoc similarity index 100% rename from prdoc/pr_3108.prdoc rename to prdoc/1.7.0/pr_3108.prdoc diff --git a/prdoc/pr_3110.prdoc b/prdoc/1.7.0/pr_3110.prdoc similarity index 100% rename from prdoc/pr_3110.prdoc rename to prdoc/1.7.0/pr_3110.prdoc diff --git a/prdoc/pr_3156.prdoc b/prdoc/1.7.0/pr_3156.prdoc similarity index 100% rename from prdoc/pr_3156.prdoc rename to prdoc/1.7.0/pr_3156.prdoc diff --git a/prdoc/1.7.0/pr_3228.prdoc b/prdoc/1.7.0/pr_3228.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..42a4893af3620057bbb4351459f26bebdefe79f9 --- /dev/null +++ b/prdoc/1.7.0/pr_3228.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet_xcm] Forgotten migration to XCMv4 + added `try-state` to the `pallet_xcm`" + +doc: + - audience: Runtime Dev + description: | + The current release includes the new `XCMv4`, so the runtimes must incorporate + a migration `pallet_xcm::migration::MigrateToLatestXcmVersion` to ensure + proper data migration in `pallet_xcm`. + +crates: + - name: pallet-xcm diff --git a/prdoc/pr_1660.prdoc b/prdoc/pr_1660.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dda38c9549595526bb438e9358ed792c6bc1406c --- /dev/null +++ b/prdoc/pr_1660.prdoc @@ -0,0 +1,12 @@ +title: Implements a percentage cap on staking rewards from era inflation + +doc: + - audience: Runtime Dev + description: | + The `pallet-staking` exposes a new perbill configuration, `MaxStakersRewards`, which caps the + amount of era inflation that is distributed to the stakers. The remainder of the era + inflation is minted directly into `T::RewardRemainder` account. This allows the runtime to be + configured to assign a minimum inflation value per era to a specific account (e.g. treasury). + +crates: + - name: pallet-staking diff --git a/prdoc/pr_2061.prdoc b/prdoc/pr_2061.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..07df11ca0543a5c90a60996f73e96b059a7e8fe4 --- /dev/null +++ b/prdoc/pr_2061.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add Parameters Pallet + +doc: + - audience: Runtime Dev + description: | + Adds `pallet-parameters` that allows to have parameters for pallet configs that dynamically change at runtime. Allows to be permissioned on a per-key basis and is compatible with ORML macros. + +crates: + - name: "pallet-parameters" + - name: "frame-support" + - name: "frame-support-procedural" diff --git a/prdoc/pr_2290.prdoc b/prdoc/pr_2290.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..9f0476e9152641fa0bacd3e71af77ca92bcd142f --- /dev/null +++ b/prdoc/pr_2290.prdoc @@ -0,0 +1,10 @@ +title: im-online pallet offcain storage cleanup + +doc: + - audience: Runtime Dev + description: | + Adds a function `clear_offchain_storage` to `pallet-im-online`. This function can be used + after the pallet was removed to clear its offchain storage. + +crates: + - name: pallet-im-online diff --git a/prdoc/pr_2903.prdoc b/prdoc/pr_2903.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..dd22fa0eea3754436e1dca0a9657eb1be8fb8151 --- /dev/null +++ b/prdoc/pr_2903.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Implement `ConversionToAssetBalance` in asset-rate" + +doc: + - audience: Runtime Dev + description: | + Implements the `ConversionToAssetBalance` trait to the asset-rate pallet. + + Previously only the `ConversionFromAssetBalance` trait was implemented, which would allow to convert an asset balance into the corresponding native balance. + + The `ConversionToAssetBalance` allows to use pallet-asset-rate, e.g., as a mechanism to charge XCM fees in an asset that is not the native. +crates: [ ] + diff --git a/prdoc/pr_2949-async-backing-on-all-testnet-system-chains.prdoc b/prdoc/pr_2949-async-backing-on-all-testnet-system-chains.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..265940815bad021bf28d549ea9a23e2fd8ce584c --- /dev/null +++ b/prdoc/pr_2949-async-backing-on-all-testnet-system-chains.prdoc @@ -0,0 +1,23 @@ +title: Enable async backing on all testnet system chains + +doc: + - audience: Runtime User + description: | + Async backing has been enabled on all testnet system chains: asset-hub-westend, + bridge-hub-westend, bridge-hub-rococo, collectives-westend, contracts-rococo, + coretime-westend, coretime-rococo, people-westend, and people-rococo. These now target 6s + block times. + For the running coretime chains, that requires updating the configuration after the runtime + upgrade but before the end of the current region. + +crates: + - name: asset-hub-westend-runtime + - name: bridge-hub-westend-runtime + - name: bridge-hub-rococo-runtime + - name: collectives-westend-runtime + - name: contracts-rococo-runtime + - name: coretime-westend-runtime + - name: coretime-rococo-runtime + - name: people-westend-runtime + - name: people-rococo-runtime + - name: polkadot-parachain-bin diff --git a/prdoc/pr_3002.prdoc b/prdoc/pr_3002.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..511a07e39c47d5055bd161fda0e748f96e4a9997 --- /dev/null +++ b/prdoc/pr_3002.prdoc @@ -0,0 +1,29 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: PoV Reclaim Runtime Side +author: skunert +topic: runtime +doc: + - audience: Runtime Dev + description: | + Adds a mechanism to reclaim proof size weight. + 1. Introduces a new `SignedExtension` that reclaims the difference + between benchmarked proof size weight and actual consumed proof size weight. + 2. Introduces a manual mechanism, `StorageWeightReclaimer`, to reclaim excess storage weight for situations + that require manual weight management. The most prominent case is the `on_idle` hook. + 3. Adds the `storage_proof_size` host function to the PVF. Parachain nodes should add it to ensure compatibility. + + To enable proof size reclaiming, add the host `storage_proof_size` host function to the parachain node. Add the + `StorageWeightReclaim` `SignedExtension` to your runtime and enable proof recording during block import. + + +crates: + - name: "cumulus-primitives-storage-weight-reclaim" +host_functions: + - name: "storage_proof_size" + description: | + This host function is used to pass the current size of the storage proof to the runtime. + It was introduced before but becomes relevant now. + Note: This host function is intended to be used through `cumulus_primitives_storage_weight_reclaim::get_proof_size`. + Direct usage is not recommended. diff --git a/prdoc/pr_3007.prdoc b/prdoc/pr_3007.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..17870469a21988f46a8338599f53562e4cd9acff --- /dev/null +++ b/prdoc/pr_3007.prdoc @@ -0,0 +1,16 @@ +title: Try State Hook for Ranked Collective. + +doc: + - audience: Runtime User + description: | + Invariants for storage items in the ranked collective pallet. Enforces the following Invariants: + 1. Total number of `Members` in storage should be >= [`MemberIndex`] of a [`Rank`] in `MemberCount`. + 2. `Rank` in Members should be in `MemberCount`. + 3.`Sum` of `MemberCount` index should be the same as the sum of all the index attained for + rank possessed by `Members` + 4. `Member` in storage of `IdToIndex` should be the same as `Member` in `IndexToId`. + 5. `Rank` in `IdToIndex` should be the same as the the `Rank` in `IndexToId`. + 6. `Rank` of the member `who` in `IdToIndex` should be the same as the `Rank` of + the member `who` in `Members` +crates: +- name: pallet-ranked-collective diff --git a/prdoc/pr_3052.prdoc b/prdoc/pr_3052.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..09f3cf59e4da467e277aeb3d36c737cd8e6a2a85 --- /dev/null +++ b/prdoc/pr_3052.prdoc @@ -0,0 +1,15 @@ +title: "Fixes a scenario where a nomination pool's `TotalValueLocked` is out of sync due to staking's implicit withdraw" + +doc: + - audience: Runtime Dev + description: | + The nomination pools pallet `TotalValueLocked` may get out of sync if the staking pallet + does implicit withdrawal of unlocking chunks belonging to a bonded pool stash. This fix + is based on a new method on the `OnStakingUpdate` traits, `on_withdraw`, which allows the + nomination pools pallet to adjust the `TotalValueLocked` every time there is an implicit or + explicit withdrawal from a bonded pool's stash. + +crates: + - name: "pallet-nomination-pools" + - name: "pallet-staking" + - name: "sp-staking" diff --git a/prdoc/pr_3060.prdoc b/prdoc/pr_3060.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..4cd6674ebb2e91b03ed0f4c5d1626bae58c82286 --- /dev/null +++ b/prdoc/pr_3060.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add retry mechanics to `pallet-scheduler` + +doc: + - audience: Runtime Dev + description: | + This PR adds retry mechanics to pallet-scheduler, as described in the issue above. + Users can now set a retry configuration for a task so that, in case its scheduled run fails, it will be retried after a number of blocks, for a specified number of times or until it succeeds. + If a retried task runs successfully before running out of retries, its remaining retry counter will be reset to the initial value. If a retried task runs out of retries, it will be removed from the schedule. + Tasks which need to be scheduled for a retry are still subject to weight metering and agenda space, same as a regular task. Periodic tasks will have their periodic schedule put on hold while the task is retrying. + +crates: + - name: pallet-scheduler diff --git a/prdoc/pr_3079.prdoc b/prdoc/pr_3079.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c745c1ffbfe5775f205dc5510a637dbfd4e6f56c --- /dev/null +++ b/prdoc/pr_3079.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Implement transaction_unstable_broadcast and transaction_unstable_stop + +doc: + - audience: Node Dev + description: | + A new RPC class is added to handle transactions. The `transaction_unstable_broadcast` broadcasts + the provided transaction to the peers of the node, until the `transaction_unstable_stop` is called. + The APIs are marked as unstable and subject to change in the future. + To know if the transaction was added to the chain, users can decode the bodies of announced finalized blocks. + This is a low-level approach for `transactionWatch_unstable_submitAndWatch`. + +crates: [ ] diff --git a/prdoc/pr_3154.prdoc b/prdoc/pr_3154.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..2dfc1b27810cf75683a374cd15f4bcb7b11105f0 --- /dev/null +++ b/prdoc/pr_3154.prdoc @@ -0,0 +1,9 @@ +title: "Contracts: Stabilize caller_is_root API" + +doc: + - audience: Runtime Dev + description: | + Removed the `#[unstable]` attrribute on `caller_is_root` host function. + +crates: + - name: pallet-contracts diff --git a/prdoc/pr_3160.prdoc b/prdoc/pr_3160.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..22305b6635aa505f4416b88d5b9f83ebc6163f28 --- /dev/null +++ b/prdoc/pr_3160.prdoc @@ -0,0 +1,12 @@ +title: "prospective-parachains: allow requesting a chain of backable candidates" + +topic: Node + +doc: + - audience: Node Dev + description: | + Enable requesting a chain of multiple backable candidates. Will be used by the provisioner + to build paras inherent data for elastic scaling. + +crates: + - name: polkadot-node-core-prospective-parachains diff --git a/prdoc/pr_3166.prdoc b/prdoc/pr_3166.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..adf7f50e4e965325cfc2cbc25d9f66467483f802 --- /dev/null +++ b/prdoc/pr_3166.prdoc @@ -0,0 +1,9 @@ +title: Expose internal functions used by `spawn_tasks` + +doc: + - audience: Node Dev + description: | + This allows to build a custom version of `spawn_tasks` with less copy-paste required + +crates: + - name: sc-service diff --git a/prdoc/pr_3184.prdoc b/prdoc/pr_3184.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..36ba92bcf5a79ac75934f4fa08e1e02d78b6175e --- /dev/null +++ b/prdoc/pr_3184.prdoc @@ -0,0 +1,10 @@ +title: "Contracts: Remove no longer enforced limits from the Schedule" + +doc: + - audience: Runtime Dev + description: | + The limits are no longer in use and do nothing. Every builder overwritting them + can just adapt their code to remove them without any consequence. + +crates: + - name: pallet-contracts diff --git a/prdoc/pr_3212.prdoc b/prdoc/pr_3212.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a9de1e28cdfe2294948fb3c954b9b417c09d149b --- /dev/null +++ b/prdoc/pr_3212.prdoc @@ -0,0 +1,9 @@ +title: "Ranked collective introduce `Add` and `Remove` origins" + +doc: + - audience: Runtime Dev + description: | + Add two new origins to the ranked-collective pallet. One to add new members and one to remove members, named `AddOrigin` and `RemoveOrigin` respectively. + +crates: + - name: pallet-ranked-collective diff --git a/prdoc/pr_3225.prdoc b/prdoc/pr_3225.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1dec40ccfbe96f5457aeea6235143b95f1ec9754 --- /dev/null +++ b/prdoc/pr_3225.prdoc @@ -0,0 +1,11 @@ +title: "Introduce submit_finality_proof_ex call to bridges GRANDPA pallet" + +doc: + - audience: Runtime Dev + description: | + New call has been added to pallet-bridge-grandpa: submit_finality_proof_ex. It should be + used instead of deprecated submit_finality_proof. submit_finality_proof will be removed + later. + +crates: + - name: "pallet-bridge-grandpa" diff --git a/prdoc/pr_3230.prdoc b/prdoc/pr_3230.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..e6d32f918d3911d691e90ff83fd6bc6695da4c25 --- /dev/null +++ b/prdoc/pr_3230.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: rpc server remove prometheus metrics `substrate_rpc_requests_started/finished` and refactor WS ping/pongs. + +doc: + - audience: Node Operator + description: | + This PR updates the rpc server library to `jsonrpsee v0.22` to utilize new APIs. + + Breaking changes: + - Remove prometheus RPC metrics `substrate_rpc_requests_started` and `substrate_rpc_requests_finished`. + - The RPC server now disconnects inactive peers that didn't acknowledge WebSocket + pings more than three times in time. + + Added: + - Add prometheus RPC `substrate_rpc_sessions_time` to collect the duration for each WebSocket + session. +crates: [ ] diff --git a/prdoc/pr_3231.prdoc b/prdoc/pr_3231.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..26e96d3635b14634ca523797d741ddaf224aa9ae --- /dev/null +++ b/prdoc/pr_3231.prdoc @@ -0,0 +1,11 @@ +title: Allow parachain which acquires multiple coretime cores to make progress + +doc: + - audience: Node Operator + description: | + Adds the needed changes so that parachains which acquire multiple coretime cores can still make progress. + Only one of the cores will be able to be occupied at a time. + Only works if the ElasticScalingMVP node feature is enabled in the runtime and the block author validator is + updated to include this change. + +crates: [ ] diff --git a/prdoc/pr_3232.prdoc b/prdoc/pr_3232.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c6a339931c6f56772e0fbaa3a4a8992e7b3050c9 --- /dev/null +++ b/prdoc/pr_3232.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Validate code when scheduling uprades + +doc: + - audience: Runtime User + description: | + Adds checks to ensure that the validation code is valid before scheduling + a code upgrade. + +crates: + - name: polkadot-runtime-parachains diff --git a/prdoc/pr_3243.prdoc b/prdoc/pr_3243.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..5bad985a2672453b56ee3828483d7035b71d4b79 --- /dev/null +++ b/prdoc/pr_3243.prdoc @@ -0,0 +1,13 @@ +title: Don't fail fast if the weight limit of a cross contract call is too big + +doc: + - audience: Runtime Dev + description: | + Cross contracts calls will now be executed even if the supplied weight + limit is bigger than the reamining weight. If the **actual** weight is too low + they will fail in the cross contract call and roll back. This is different from the + old behaviour where the limit for the cross contract call must be smaller than + the remaining weight. + +crates: + - name: pallet-contracts diff --git a/prdoc/pr_3244.prdoc b/prdoc/pr_3244.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..3d851c8afe0d2453a943caa1f0342d7ec76fe53c --- /dev/null +++ b/prdoc/pr_3244.prdoc @@ -0,0 +1,18 @@ +title: "Make the `benchmark pallet` command only require a Hasher" + +doc: + - audience: Node Dev + description: | + Currently the `benchmark pallet` command requires a `Block` type, while only using its hasher. + Now this is changed to only require the hasher. This means to use `HashingFor` in the + place where `Block` was required. + Example patch for your node with `cmd` being `BenchmarkCmd::Pallet(cmd)`: + + ```patch + - cmd.run::(config) + + cmd.run::, ()>(config) + ``` + +crates: + - name: sc-client-db + - name: frame-benchmarking-cli diff --git a/prdoc/pr_3272.prdoc b/prdoc/pr_3272.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a03113cf9739b741a37c5d8b4fb41dd59ede0d9f --- /dev/null +++ b/prdoc/pr_3272.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Westend Coretime Runtime + +doc: + - audience: Runtime User + description: | + Add the Broker pallet to the Westend Coretime Chain runtime for the main functionality and sales to start. + +crates: [ ] diff --git a/prdoc/pr_3301.prdoc b/prdoc/pr_3301.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..19d1c5f1c18907cd1996603aade66990c358efeb --- /dev/null +++ b/prdoc/pr_3301.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: rpc server add rate limiting. + +doc: + - audience: Node Operator + description: | + Add rate limiting for RPC server which can be utilized by the CLI `--rpc-rate-limit ` + The rate-limiting is disabled by default. +crates: [ ] diff --git a/prdoc/pr_3308.prdoc b/prdoc/pr_3308.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..386cbd6230b42144741f9a85490cddb009221ab2 --- /dev/null +++ b/prdoc/pr_3308.prdoc @@ -0,0 +1,14 @@ +title: "Parachains-Aura: Only produce once per slot" + +doc: + - audience: Node Dev + description: | + With the introduction of asynchronous backing the relay chain allows parachain to include blocks every 6 seconds. + The Cumulus Aura implementations, besides the lookahead collator, are building blocks when there is a free slot for + the parachain in the relay chain. Most parachains are still running with a 12s slot duration and not allowing + to build multiple blocks per slot. But, the block production logic will be triggered every 6s, resulting in error + logs like: "no space left for the block in the unincluded segment". This is solved by ensuring that we don't build + multiple blocks per slot. + +crates: + - name: "cumulus-client-consensus-aura" diff --git a/prdoc/pr_3319.prdoc b/prdoc/pr_3319.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b84ec25a22eda2a561fb44eb1e732a3c36921418 --- /dev/null +++ b/prdoc/pr_3319.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add Coretime to Westend + +doc: + - audience: Runtime User + description: | + Add the on demand and coretime assigners and migrate from legacy parachain auctions to coretime. + +crates: [ ] diff --git a/prdoc/pr_3324.prdoc b/prdoc/pr_3324.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..0425fbf317c8620125e9fe64f96a0eb99e4595f0 --- /dev/null +++ b/prdoc/pr_3324.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Fix weight calculation and event emission in pallet-membership" + +doc: + - audience: Runtime Dev + description: | + Bug fix for the membership pallet to use correct weights. Also no event will be emitted + anymore when `change_key` is called with identical accounts. + +crates: + - name: pallet-membership diff --git a/prdoc/pr_3325.prdoc b/prdoc/pr_3325.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..eb8126dc912520c049f44fb760bb759caf6336f9 --- /dev/null +++ b/prdoc/pr_3325.prdoc @@ -0,0 +1,10 @@ +title: "Ensure `TracksInfo` tracks are sorted by ID." + +doc: + - audience: Runtime Dev + description: | + Add a `integrity_check` function to trait `TracksInfo` and explicitly state that tracks must + always be sorted by ID. The referenda pallet now also uses this check in its `integrity_test`. + +crates: + - name: pallet-referenda diff --git a/prdoc/pr_3358.prdoc b/prdoc/pr_3358.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..b6a03b3872f4e7d24305dfd924ccb8424f6e39b2 --- /dev/null +++ b/prdoc/pr_3358.prdoc @@ -0,0 +1,11 @@ +title: Do not stall finality on spam disputes + +doc: + - audience: Node Operator + description: | + This PR fixes the issue that periodically caused + finality stalls on Kusama due to disputes happening + there in combination with disputes spam protection mechanism. + See: https://github.com/paritytech/polkadot-sdk/issues/3345 + +crates: [ ] diff --git a/prdoc/pr_3361.prdoc b/prdoc/pr_3361.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..65baa9e94a0e38a4af16bf6f69aab7526c4ad920 --- /dev/null +++ b/prdoc/pr_3361.prdoc @@ -0,0 +1,10 @@ +title: Fix double charge of host function weight + +doc: + - audience: Runtime Dev + description: | + Fixed a double charge which can lead to quadratic gas consumption of + the `call` and `instantiate` host functions. + +crates: + - name: pallet-contracts diff --git a/prdoc/pr_3364.prdoc b/prdoc/pr_3364.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1e7a6a5278d730a71a0bf5a928815812d8576386 --- /dev/null +++ b/prdoc/pr_3364.prdoc @@ -0,0 +1,12 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: rpc server expose batch request configuration + +doc: + - audience: Node Operator + description: | + Add functionality to limit RPC batch requests by two new CLI options: + --rpc-disable-batch-request - disable batch requests on the server + --rpc-max-batch-request-len - limit batches to LEN on the server +crates: [ ] diff --git a/prdoc/pr_3370.prdoc b/prdoc/pr_3370.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..f40d3821b9fed3eae49803128a9ff61058cd5d02 --- /dev/null +++ b/prdoc/pr_3370.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Remove `key` getter from pallet-sudo" + +doc: + - audience: Runtime Dev + description: | + Removed the `key` getter function from the sudo pallet. There is no replacement for getting + the key currently. + +crates: + - name: pallet-sudo diff --git a/prdoc/pr_3371.prdoc b/prdoc/pr_3371.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..605d540772f0b249962088463237dda2e6690d6c --- /dev/null +++ b/prdoc/pr_3371.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: removed `pallet::getter` from example pallets + +doc: + - audience: Runtime Dev + description: | + This PR removes all the `pallet::getter` usages from the template pallets found in the Substrate and Cumulus template nodes, and from the Substrate example pallets. + The purpose is to discourage developers to use this macro, that is currently being removed and soon will be deprecated. + +crates: + - name: pallet-template + - name: pallet-parachain-template + - name: pallet-example-basic + - name: pallet-example-kitchensink + - name: pallet-example-offchain-worker + - name: pallet-example-split + diff --git a/prdoc/pr_3384.prdoc b/prdoc/pr_3384.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c19464977582a37b11760c81491a7ea7cf3ebced --- /dev/null +++ b/prdoc/pr_3384.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet_contracts] stabilize `call_v2`, `instantiate_v2`, `lock_dependency` and `unlock_dependency`" + +doc: + - audience: Runtime Dev + description: | + These APIs are currently unstable and are being stabilized in this PR. + Note: `add_delegate_dependency` and `remove_delegate_dependency` have been renamed to `lock_dependency` and `unlock_dependency` respectively. + +crates: + - name: pallet-contracts-uapi + - name: pallet-contracts diff --git a/prdoc/pr_3395.prdoc b/prdoc/pr_3395.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..a10fb84fd7f56211602fadaf656a25ea5d382255 --- /dev/null +++ b/prdoc/pr_3395.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "`benchmarking-cli` `pallet` subcommand: refactor `--list` and add `--all` option" + +doc: + - audience: Node Dev + description: | + `pallet` subcommand's `--list` now accepts two values: "all" and "pallets". The former will list all available benchmarks, the latter will list only pallets. + Also adds `--all` to run all the available benchmarks and `--no-csv-header` to omit the csv-style header in the output. + NOTE: changes are backward compatible. + +crates: + - name: frame-benchmarking-cli diff --git a/prdoc/pr_3411.prdoc b/prdoc/pr_3411.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..d847d6756ac7f019cf12e4d9e7993ebeda1273ab --- /dev/null +++ b/prdoc/pr_3411.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: add Encointer as trusted teleporter for Westend + +doc: + - audience: Runtime Dev + description: | + add Encointer as trusted teleporter for Westend with ParaId 1003 + +crates: [ ] diff --git a/prdoc/pr_3412.prdoc b/prdoc/pr_3412.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1ee6edfeb837f81646d32633ed009d2cded4dcfe --- /dev/null +++ b/prdoc/pr_3412.prdoc @@ -0,0 +1,17 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[FRAME] Add genesis test and remove some checks" + +doc: + - audience: Runtime Dev + description: | + The construct_runtime macro now generates a test to assert that all `GenesisConfig`s of all + pallets can be build within the runtime. This ensures that the `BuildGenesisConfig` runtime + API works. + Further, some checks from a few pallets were removed to make this pass. + +crates: + - name: pallet-babe + - name: cumulus-pallet-aura-ext + - name: pallet-session diff --git a/prdoc/pr_3415.prdoc b/prdoc/pr_3415.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..c56a5d3ffaa129d4aba36e1e11905e9d72b936cb --- /dev/null +++ b/prdoc/pr_3415.prdoc @@ -0,0 +1,9 @@ +title: "[pallet-contracts] Add APIVersion to the config." + +doc: + - audience: Runtime Dev + description: | + Add `APIVersion` to the config to communicate the state of the Host functions exposed by the pallet. + +crates: + - name: pallet-contracts diff --git a/prdoc/pr_3435.prdoc b/prdoc/pr_3435.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..7d7896bff7d405a0a1dd34f09bc2aff0834b9ff2 --- /dev/null +++ b/prdoc/pr_3435.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix BEEFY-related gossip messages error logs + +doc: + - audience: Node Operator + description: | + Added logic to pump the gossip engine while waiting for other things + to make sure gossiped messages get consumed (practically discarded + until worker is fully initialized). + This fixes an issue where node operators saw error logs, and fixes + potential RAM bloat when BEEFY initialization takes a long time + (i.e. during clean sync). +crates: + - name: sc-consensus-beefy diff --git a/prdoc/pr_3447.prdoc b/prdoc/pr_3447.prdoc new file mode 100644 index 0000000000000000000000000000000000000000..1d8d4f409f77cf29247d35d196bccff390896881 --- /dev/null +++ b/prdoc/pr_3447.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use generic hash for runtime wasm in resolve_state_version_from_wasm + +doc: + - audience: Node Dev + description: | + Changes the runtime hash algorithm used in resolve_state_version_from_wasm from DefaultHasher to a caller-provided + one (usually HashingFor). Fixes a bug where the runtime wasm was being compiled again when it was not + needed, because the hash did not match +crates: + - name: sc-chain-spec diff --git a/substrate/bin/minimal/node/Cargo.toml b/substrate/bin/minimal/node/Cargo.toml index 34476a72954dd31e98cbb56f266ebba447a46513..65644861c9acaca95c6b152e7d237badbed89891 100644 --- a/substrate/bin/minimal/node/Cargo.toml +++ b/substrate/bin/minimal/node/Cargo.toml @@ -20,11 +20,11 @@ targets = ["x86_64-unknown-linux-gnu"] name = "minimal-node" [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" -jsonrpsee = { version = "0.20.3", features = ["server"] } -serde_json = "1.0.111" +jsonrpsee = { version = "0.22", features = ["server"] } +serde_json = { workspace = true, default-features = true } sc-cli = { path = "../../../client/cli" } sc-executor = { path = "../../../client/executor" } diff --git a/substrate/bin/node-template/.envrc b/substrate/bin/node-template/env-setup/.envrc similarity index 100% rename from substrate/bin/node-template/.envrc rename to substrate/bin/node-template/env-setup/.envrc diff --git a/substrate/bin/node-template/env-setup/README.md b/substrate/bin/node-template/env-setup/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a7955872d3ff2172bc1c818e597a5b165005bb62 --- /dev/null +++ b/substrate/bin/node-template/env-setup/README.md @@ -0,0 +1,9 @@ +# Env setup + +Special files for setting up an environment to work with the template: + +- `rust-toolchain.toml` when working with `rustup`. +- `flake.nix` when working with `nix`. + +These files will be copied by the installer script to the main directory. They are +put into this special directory to not interfere with the normal CI. diff --git a/substrate/bin/node-template/flake.lock b/substrate/bin/node-template/env-setup/flake.lock similarity index 100% rename from substrate/bin/node-template/flake.lock rename to substrate/bin/node-template/env-setup/flake.lock diff --git a/substrate/bin/node-template/flake.nix b/substrate/bin/node-template/env-setup/flake.nix similarity index 100% rename from substrate/bin/node-template/flake.nix rename to substrate/bin/node-template/env-setup/flake.nix diff --git a/substrate/bin/node-template/rust-toolchain.toml b/substrate/bin/node-template/env-setup/rust-toolchain.toml similarity index 90% rename from substrate/bin/node-template/rust-toolchain.toml rename to substrate/bin/node-template/env-setup/rust-toolchain.toml index 2a35c6ed07c1c2a667729d1fa558e3da0cd0457f..f81199a2249993fce8c98bc4a721d77c6c91d3d7 100644 --- a/substrate/bin/node-template/rust-toolchain.toml +++ b/substrate/bin/node-template/env-setup/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly" +channel = "stable" components = [ "cargo", "clippy", diff --git a/substrate/bin/node-template/node/Cargo.toml b/substrate/bin/node-template/node/Cargo.toml index da601a665e9c33bf923bf1f3b32e630a5daeef39..9cba2b5a36602cae3687b8d5afaa71510cd0d926 100644 --- a/substrate/bin/node-template/node/Cargo.toml +++ b/substrate/bin/node-template/node/Cargo.toml @@ -20,9 +20,9 @@ targets = ["x86_64-unknown-linux-gnu"] name = "node-template" [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"] } -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } sc-cli = { path = "../../../client/cli" } sp-core = { path = "../../../primitives/core" } @@ -48,7 +48,7 @@ frame-system = { path = "../../../frame/system" } pallet-transaction-payment = { path = "../../../frame/transaction-payment", default-features = false } # These dependencies are used for the node template's RPCs -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } sp-api = { path = "../../../primitives/api" } sc-rpc-api = { path = "../../../client/rpc-api" } sp-blockchain = { path = "../../../primitives/blockchain" } diff --git a/substrate/bin/node-template/node/src/command.rs b/substrate/bin/node-template/node/src/command.rs index a25157693cd4392072703c28b14310f16dc01030..3778df6642294684780c347730eb580b69b54d65 100644 --- a/substrate/bin/node-template/node/src/command.rs +++ b/substrate/bin/node-template/node/src/command.rs @@ -117,7 +117,7 @@ pub fn run() -> sc_cli::Result<()> { ) } - cmd.run::(config) + cmd.run::, ()>(config) }, BenchmarkCmd::Block(cmd) => { let PartialComponents { client, .. } = service::new_partial(&config)?; diff --git a/substrate/bin/node-template/pallets/template/src/lib.rs b/substrate/bin/node-template/pallets/template/src/lib.rs index 4a2e53baa774ee5ca333602024d458aa078f9c10..90dfe370145856cc08483cdbce2bbc542b21b2b9 100644 --- a/substrate/bin/node-template/pallets/template/src/lib.rs +++ b/substrate/bin/node-template/pallets/template/src/lib.rs @@ -90,9 +90,7 @@ pub mod pallet { /// /// In this template, we are declaring a storage item called `Something` that stores a single /// `u32` value. Learn more about runtime storage here: - /// The [`getter`] macro generates a function to conveniently retrieve the value from storage. #[pallet::storage] - #[pallet::getter(fn something)] pub type Something = StorageValue<_, u32>; /// Events that functions in this pallet can emit. @@ -187,7 +185,7 @@ pub mod pallet { let _who = ensure_signed(origin)?; // Read a value from storage. - match Pallet::::something() { + match Something::::get() { // Return an error if the value has not been set. None => Err(Error::::NoneValue.into()), Some(old) => { diff --git a/substrate/bin/node-template/pallets/template/src/tests.rs b/substrate/bin/node-template/pallets/template/src/tests.rs index 7c2b853ee4dc56fdf526a17557fe37281a50e803..83e4bea7377b348d8ac1d389813a788f79b81970 100644 --- a/substrate/bin/node-template/pallets/template/src/tests.rs +++ b/substrate/bin/node-template/pallets/template/src/tests.rs @@ -1,4 +1,4 @@ -use crate::{mock::*, Error, Event}; +use crate::{mock::*, Error, Event, Something}; use frame_support::{assert_noop, assert_ok}; #[test] @@ -9,7 +9,7 @@ fn it_works_for_default_value() { // 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_eq!(Something::::get(), Some(42)); // Assert that the correct event was deposited System::assert_last_event(Event::SomethingStored { something: 42, who: 1 }.into()); }); diff --git a/substrate/bin/node-template/runtime/Cargo.toml b/substrate/bin/node-template/runtime/Cargo.toml index a7b93a230ca85dd0b400884b4788a50d24db0402..86bab0ca375a84cad0fc9ac2faf5fbc105c0b001 100644 --- a/substrate/bin/node-template/runtime/Cargo.toml +++ b/substrate/bin/node-template/runtime/Cargo.toml @@ -42,7 +42,7 @@ sp-std = { path = "../../../primitives/std", default-features = false } sp-storage = { path = "../../../primitives/storage", default-features = false } sp-transaction-pool = { path = "../../../primitives/transaction-pool", default-features = false } sp-version = { path = "../../../primitives/version", default-features = false, features = ["serde"] } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } +serde_json = { features = ["alloc"], workspace = true } sp-genesis-builder = { default-features = false, path = "../../../primitives/genesis-builder" } # Used for the node template's RPCs diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 42af802d716b7181b91310318137928896c6bd66..8e0f7fb93b3904ac20902662326a7cba067fbf21 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -16,16 +16,16 @@ workspace = true [dependencies] array-bytes = "6.1" -clap = { version = "4.4.18", features = ["derive"] } -log = "0.4.17" +clap = { version = "4.5.1", features = ["derive"] } +log = { workspace = true, default-features = true } node-primitives = { path = "../primitives" } node-testing = { path = "../testing" } kitchensink-runtime = { path = "../runtime" } sc-client-api = { path = "../../../client/api" } sp-runtime = { path = "../../../primitives/runtime" } sp-state-machine = { path = "../../../primitives/state-machine" } -serde = "1.0.195" -serde_json = "1.0.111" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } derive_more = { version = "0.99.17", default-features = false, features = ["display"] } kvdb = "0.13.0" kvdb-rocksdb = "0.19.0" diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 5dfe915b789d5e2e22b56d786513214e9c4e26a6..abe284c41da1f21c064cebcf05ee23c9f4f540f0 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -41,12 +41,12 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies array-bytes = "6.1" -clap = { version = "4.4.18", features = ["derive"], optional = true } +clap = { version = "4.5.1", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.6.1" } -serde = { version = "1.0.195", features = ["derive"] } -jsonrpsee = { version = "0.20.3", features = ["server"] } +serde = { features = ["derive"], workspace = true, default-features = true } +jsonrpsee = { version = "0.22", features = ["server"] } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } rand = "0.8" # primitives @@ -116,7 +116,7 @@ sc-cli = { path = "../../../client/cli", optional = true } frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli", optional = true } node-inspect = { package = "staging-node-inspect", path = "../inspect", optional = true } try-runtime-cli = { path = "../../../utils/frame/try-runtime/cli", optional = true } -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } [dev-dependencies] sc-keystore = { path = "../../../client/keystore" } @@ -159,13 +159,13 @@ sp-consensus-babe = { path = "../../../primitives/consensus/babe" } sp-externalities = { path = "../../../primitives/externalities" } sp-keyring = { path = "../../../primitives/keyring" } sp-runtime = { path = "../../../primitives/runtime" } -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } scale-info = { version = "2.10.0", features = ["derive", "serde"] } sp-trie = { path = "../../../primitives/trie" } sp-state-machine = { path = "../../../primitives/state-machine" } [build-dependencies] -clap = { version = "4.4.18", optional = true } +clap = { version = "4.5.1", optional = true } clap_complete = { version = "4.0.2", optional = true } node-inspect = { package = "staging-node-inspect", path = "../inspect", optional = true } frame-benchmarking-cli = { path = "../../../utils/frame/benchmarking-cli", optional = true } diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs index c17c12dfef13e49662fe1ed8c73a9499e00535ec..23a62cc0bd243ebd1c8ade712b3ded2361f9e4c0 100644 --- a/substrate/bin/node/cli/benches/block_production.rs +++ b/substrate/bin/node/cli/benches/block_production.rs @@ -28,7 +28,7 @@ use sc_consensus::{ use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, - PruningMode, WasmExecutionMethod, WasmtimeInstantiationStrategy, + PruningMode, RpcBatchRequestConfig, WasmExecutionMethod, WasmtimeInstantiationStrategy, }, BasePath, Configuration, Role, }; @@ -84,6 +84,8 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs index 0d0d3a072d89dd18ddf0362bc4b73dab54f8df51..de4eef1944d41bc6f37d06819ad70ca0a6dad109 100644 --- a/substrate/bin/node/cli/benches/transaction_pool.rs +++ b/substrate/bin/node/cli/benches/transaction_pool.rs @@ -26,7 +26,7 @@ use node_primitives::AccountId; use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, - PruningMode, TransactionPoolOptions, + PruningMode, RpcBatchRequestConfig, TransactionPoolOptions, }, BasePath, Configuration, Role, }; @@ -80,6 +80,8 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/bin/node/cli/src/command.rs b/substrate/bin/node/cli/src/command.rs index dc28705c2aea9323d0ce84ae901d0206fe513efe..9645173202866a7334ab3eaacd948132d9b3c9d7 100644 --- a/substrate/bin/node/cli/src/command.rs +++ b/substrate/bin/node/cli/src/command.rs @@ -28,6 +28,7 @@ use node_primitives::Block; use sc_cli::{Result, SubstrateCli}; use sc_service::PartialComponents; use sp_keyring::Sr25519Keyring; +use sp_runtime::traits::HashingFor; use std::sync::Arc; @@ -106,7 +107,7 @@ pub fn run() -> Result<()> { ) } - cmd.run::(config) + cmd.run::, sp_statement_store::runtime_api::HostFunctions>(config) }, BenchmarkCmd::Block(cmd) => { // ensure that we keep the task manager alive diff --git a/substrate/bin/node/cli/tests/res/default_genesis_config.json b/substrate/bin/node/cli/tests/res/default_genesis_config.json index 1465a6497cadb3d79f75910dc2e084c168559c62..e21fbb47da8c4619e0923c85bf3470828cd80b23 100644 --- a/substrate/bin/node/cli/tests/res/default_genesis_config.json +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -2,7 +2,13 @@ "system": {}, "babe": { "authorities": [], - "epochConfig": null + "epochConfig": { + "allowed_slots": "PrimaryAndSecondaryVRFSlots", + "c": [ + 1, + 4 + ] + } }, "indices": { "indices": [] diff --git a/substrate/bin/node/inspect/Cargo.toml b/substrate/bin/node/inspect/Cargo.toml index e703e312b51bb144cc1c2dd18b927d7d40908b3c..7db6e3efd3a0fccc7ddd624e6ecb029c82637c03 100644 --- a/substrate/bin/node/inspect/Cargo.toml +++ b/substrate/bin/node/inspect/Cargo.toml @@ -15,9 +15,9 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } -thiserror = "1.0" +thiserror = { workspace = true } sc-cli = { path = "../../../client/cli" } sc-client-api = { path = "../../../client/api" } sc-service = { path = "../../../client/service", default-features = false } diff --git a/substrate/bin/node/rpc/Cargo.toml b/substrate/bin/node/rpc/Cargo.toml index 63a30965a160603094a1058b30dcd026ec18b6a0..894dbf0da85ca56d412087adf01fa12c7983ae7a 100644 --- a/substrate/bin/node/rpc/Cargo.toml +++ b/substrate/bin/node/rpc/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["server"] } +jsonrpsee = { version = "0.22", features = ["server"] } node-primitives = { path = "../primitives" } pallet-transaction-payment-rpc = { path = "../../../frame/transaction-payment/rpc" } mmr-rpc = { path = "../../../client/merkle-mountain-range/rpc" } diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 4bb5fed2b09a210ff4915609b1b825746ed4f514..aa13c3d292be6af6d6dba12e5b043a9069eab9a3 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -25,8 +25,8 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } static_assertions = "1.1.0" -log = { version = "0.4.17", default-features = false } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc", "arbitrary_precision"] } +log = { workspace = true } +serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } # pallet-asset-conversion: turn on "num-traits" feature primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "num-traits", "scale-info"] } @@ -142,6 +142,7 @@ pallet-vesting = { path = "../../../frame/vesting", default-features = false } pallet-whitelist = { path = "../../../frame/whitelist", default-features = false } pallet-tx-pause = { path = "../../../frame/tx-pause", default-features = false } pallet-safe-mode = { path = "../../../frame/safe-mode", default-features = false } +pallet-parameters = { path = "../../../frame/parameters", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../utils/wasm-builder", optional = true } @@ -209,6 +210,7 @@ std = [ "pallet-nomination-pools/std", "pallet-offences-benchmarking?/std", "pallet-offences/std", + "pallet-parameters/std", "pallet-preimage/std", "pallet-proxy/std", "pallet-ranked-collective/std", @@ -310,6 +312,7 @@ runtime-benchmarks = [ "pallet-nomination-pools/runtime-benchmarks", "pallet-offences-benchmarking/runtime-benchmarks", "pallet-offences/runtime-benchmarks", + "pallet-parameters/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-ranked-collective/runtime-benchmarks", @@ -386,6 +389,7 @@ try-runtime = [ "pallet-nis/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-offences/try-runtime", + "pallet-parameters/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", "pallet-ranked-collective/try-runtime", diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 94bc223b4c5c9ed4aaa4ecfb2c3cce6ef7838fcf..b96594567e22c4d4461d23484964e96e7d15412f 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -30,6 +30,7 @@ use frame_election_provider_support::{ use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, + dynamic_params::{dynamic_pallet_params, dynamic_params}, genesis_builder_helper::{build_config, create_default_config}, instances::{Instance1, Instance2}, ord_parameter_types, @@ -44,9 +45,9 @@ use frame_support::{ GetSalary, PayFromAccount, }, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Contains, Currency, - EitherOfDiverse, EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, - KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, - WithdrawReasons, + EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, + InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, + OnUnbalanced, WithdrawReasons, }, weights::{ constants::{ @@ -457,9 +458,6 @@ impl pallet_glutton::Config for Runtime { } parameter_types! { - pub const PreimageBaseDeposit: Balance = 1 * DOLLARS; - // One cent: $10,000 / MB - pub const PreimageByteDeposit: Balance = 1 * CENTS; pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } @@ -472,7 +470,11 @@ impl pallet_preimage::Config for Runtime { AccountId, Balances, PreimageHoldReason, - LinearStoragePrice, + LinearStoragePrice< + dynamic_params::storage::BaseDeposit, + dynamic_params::storage::ByteDeposit, + Balance, + >, >; } @@ -1012,6 +1014,8 @@ impl pallet_referenda::Config for Runtime { impl pallet_ranked_collective::Config for Runtime { type WeightInfo = pallet_ranked_collective::weights::SubstrateWeight; type RuntimeEvent = RuntimeEvent; + type AddOrigin = EnsureRoot; + type RemoveOrigin = Self::DemoteOrigin; type PromoteOrigin = EnsureRootWithSuccess>; type DemoteOrigin = EnsureRootWithSuccess>; type ExchangeOrigin = EnsureRootWithSuccess>; @@ -1323,9 +1327,6 @@ impl pallet_tips::Config for Runtime { } parameter_types! { - pub const DepositPerItem: Balance = deposit(1, 0); - pub const DepositPerByte: Balance = deposit(0, 1); - pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); pub Schedule: pallet_contracts::Schedule = Default::default(); pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } @@ -1343,9 +1344,9 @@ impl pallet_contracts::Config for Runtime { /// change because that would break already deployed contracts. The `Call` structure itself /// is not allowed to change the indices of existing pallets, too. type CallFilter = Nothing; - type DepositPerItem = DepositPerItem; - type DepositPerByte = DepositPerByte; - type DefaultDepositLimit = DefaultDepositLimit; + type DepositPerItem = dynamic_params::contracts::DepositPerItem; + type DepositPerByte = dynamic_params::contracts::DepositPerByte; + type DefaultDepositLimit = dynamic_params::contracts::DefaultDepositLimit; type CallStack = [pallet_contracts::Frame; 5]; type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; @@ -1365,6 +1366,7 @@ impl pallet_contracts::Config for Runtime { type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; type Debug = (); type Environment = (); + type ApiVersion = (); type Xcm = (); } @@ -2025,7 +2027,7 @@ pub struct CoretimeProvider; impl CoretimeInterface for CoretimeProvider { type AccountId = AccountId; type Balance = Balance; - type RealyChainBlockNumberProvider = System; + type RelayChainBlockNumberProvider = System; fn request_core_count(_count: CoreIndex) {} fn request_revenue_info_at(_when: u32) {} fn credit_account(_who: Self::AccountId, _amount: Self::Balance) {} @@ -2085,6 +2087,81 @@ impl pallet_mixnet::Config for Runtime { type MinMixnodes = ConstU32<7>; // Low to allow small testing networks } +/// Dynamic parameters that can be changed at runtime through the +/// `pallet_parameters::set_parameter`. +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 0)] + pub mod storage { + /// Configures the base deposit of storing some data. + #[codec(index = 0)] + pub static BaseDeposit: Balance = 1 * DOLLARS; + + /// Configures the per-byte deposit of storing some data. + #[codec(index = 1)] + pub static ByteDeposit: Balance = 1 * CENTS; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod contracts { + #[codec(index = 0)] + pub static DepositPerItem: Balance = deposit(1, 0); + + #[codec(index = 1)] + pub static DepositPerByte: Balance = deposit(0, 1); + + #[codec(index = 2)] + pub static DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters::Storage(dynamic_params::storage::Parameters::BaseDeposit( + dynamic_params::storage::BaseDeposit, + Some(1 * DOLLARS), + )) + } +} + +pub struct DynamicParametersManagerOrigin; +impl EnsureOriginWithArg for DynamicParametersManagerOrigin { + type Success = (); + + fn try_origin( + origin: RuntimeOrigin, + key: &RuntimeParametersKey, + ) -> Result { + match key { + RuntimeParametersKey::Storage(_) => { + frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; + return Ok(()) + }, + RuntimeParametersKey::Contract(_) => { + frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; + return Ok(()) + }, + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { + Ok(RuntimeOrigin::root()) + } +} + +impl pallet_parameters::Config for Runtime { + type RuntimeParameters = RuntimeParameters; + type RuntimeEvent = RuntimeEvent; + type AdminOrigin = DynamicParametersManagerOrigin; + type WeightInfo = (); +} + construct_runtime!( pub enum Runtime { System: frame_system, @@ -2166,6 +2243,7 @@ construct_runtime!( Broker: pallet_broker, TasksExample: pallet_example_tasks, Mixnet: pallet_mixnet, + Parameters: pallet_parameters, SkipFeelessPayment: pallet_skip_feeless_payment, } ); @@ -2285,6 +2363,7 @@ mod benches { [pallet_elections_phragmen, Elections] [pallet_fast_unstake, FastUnstake] [pallet_nis, Nis] + [pallet_parameters, Parameters] [pallet_grandpa, Grandpa] [pallet_identity, Identity] [pallet_im_online, ImOnline] diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml index 9ca8b8ef7265e362de2ec8b2f482efe389d71dff..31f8689d46ca30546482d438938c09457a433bf5 100644 --- a/substrate/bin/node/testing/Cargo.toml +++ b/substrate/bin/node/testing/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } fs_extra = "1" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } tempfile = "3.1.0" frame-system = { path = "../../../frame/system" } node-cli = { package = "staging-node-cli", path = "../cli" } diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index 6ec21fbe09342d1f704a4ed1a6f79b6160c27a01..c79612d68444c8bd64ad18c3b0a74ceed176eff8 100644 --- a/substrate/bin/node/testing/src/genesis.rs +++ b/substrate/bin/node/testing/src/genesis.rs @@ -20,9 +20,8 @@ use crate::keyring::*; use kitchensink_runtime::{ - constants::currency::*, AccountId, AssetsConfig, BabeConfig, BalancesConfig, GluttonConfig, - GrandpaConfig, IndicesConfig, RuntimeGenesisConfig, SessionConfig, SocietyConfig, StakerStatus, - StakingConfig, BABE_GENESIS_EPOCH_CONFIG, + constants::currency::*, AccountId, AssetsConfig, BalancesConfig, IndicesConfig, + RuntimeGenesisConfig, SessionConfig, SocietyConfig, StakerStatus, StakingConfig, }; use sp_keyring::Ed25519Keyring; use sp_runtime::Perbill; @@ -47,7 +46,6 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { endowed.extend(extra_endowed.into_iter().map(|endowed| (endowed, 100 * DOLLARS))); RuntimeGenesisConfig { - system: Default::default(), indices: IndicesConfig { indices: vec![] }, balances: BalancesConfig { balances: endowed }, session: SessionConfig { @@ -69,39 +67,8 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { invulnerables: vec![alice(), bob(), charlie()], ..Default::default() }, - babe: BabeConfig { - authorities: vec![], - epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG), - ..Default::default() - }, - grandpa: GrandpaConfig { authorities: vec![], _config: Default::default() }, - beefy: Default::default(), - im_online: Default::default(), - authority_discovery: Default::default(), - democracy: Default::default(), - council: Default::default(), - technical_committee: Default::default(), - technical_membership: Default::default(), - elections: Default::default(), - sudo: Default::default(), - treasury: Default::default(), society: SocietyConfig { pot: 0 }, - vesting: Default::default(), assets: AssetsConfig { assets: vec![(9, alice(), true, 1)], ..Default::default() }, - pool_assets: Default::default(), - transaction_storage: Default::default(), - transaction_payment: Default::default(), - alliance: Default::default(), - alliance_motion: Default::default(), - nomination_pools: Default::default(), - safe_mode: Default::default(), - tx_pause: Default::default(), - glutton: GluttonConfig { - compute: Default::default(), - storage: Default::default(), - trash_data_count: Default::default(), - ..Default::default() - }, - mixnet: Default::default(), + ..Default::default() } } diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index e39c98983800a72ed24ab64a0af07f70c2d7d48d..996372c8a0f8638ad4420dc8e21c8f1d418bb60e 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -23,8 +23,8 @@ name = "chain-spec-builder" crate-type = ["rlib"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } -log = "0.4.17" +clap = { version = "4.5.1", features = ["derive"] } +log = { workspace = true, default-features = true } sc-chain-spec = { path = "../../../client/chain-spec" } -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } sp-tracing = { path = "../../../primitives/tracing" } diff --git a/substrate/bin/utils/subkey/Cargo.toml b/substrate/bin/utils/subkey/Cargo.toml index ac97428a7dfdb9cb9edad671cdf9698000091eca..93b1368ca757ad8f383a4c0945c69d0055a8c1ff 100644 --- a/substrate/bin/utils/subkey/Cargo.toml +++ b/substrate/bin/utils/subkey/Cargo.toml @@ -20,5 +20,5 @@ path = "src/main.rs" name = "subkey" [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } sc-cli = { path = "../../../client/cli" } diff --git a/substrate/bin/utils/subkey/README.md b/substrate/bin/utils/subkey/README.md index 3232c82958727555a44168351309b6039a708d89..a5f27cfd3707d6278ac9f78b727021a305a18457 100644 --- a/substrate/bin/utils/subkey/README.md +++ b/substrate/bin/utils/subkey/README.md @@ -91,7 +91,7 @@ SS58 addresses are: ### Json output -`subkey` can calso generate the output as *json*. This is useful for automation. +`subkey` can also generate the output as *json*. This is useful for automation. command: diff --git a/substrate/client/allocator/Cargo.toml b/substrate/client/allocator/Cargo.toml index f882cda9081fd27c57f41e2b75b57ff8018e7f54..2c268b548ea9c32fabdc82226c77e82a7ef59cea 100644 --- a/substrate/client/allocator/Cargo.toml +++ b/substrate/client/allocator/Cargo.toml @@ -17,7 +17,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.17" -thiserror = "1.0.48" +log = { workspace = true, default-features = true } +thiserror = { workspace = true } sp-core = { path = "../../primitives/core" } sp-wasm-interface = { path = "../../primitives/wasm-interface" } diff --git a/substrate/client/api/Cargo.toml b/substrate/client/api/Cargo.toml index 14aca6d9a2ee1631483df3e10a4dd1549670e0d0..cd7b613e277f3952304d70c3bf63c36d09432bc2 100644 --- a/substrate/client/api/Cargo.toml +++ b/substrate/client/api/Cargo.toml @@ -22,7 +22,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = ] } fnv = "1.0.6" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-executor = { path = "../executor" } @@ -41,6 +41,6 @@ sp-storage = { path = "../../primitives/storage" } sp-trie = { path = "../../primitives/trie" } [dev-dependencies] -thiserror = "1.0.48" +thiserror = { workspace = true } sp-test-primitives = { path = "../../primitives/test-primitives" } substrate-test-runtime = { path = "../../test-utils/runtime" } diff --git a/substrate/client/api/src/client.rs b/substrate/client/api/src/client.rs index 46232c74539c6c230652a753b5fcd4b6761600d8..2de09840e4dfdc15e1f1d1acaa5f3e438de0caca 100644 --- a/substrate/client/api/src/client.rs +++ b/substrate/client/api/src/client.rs @@ -278,7 +278,7 @@ impl fmt::Display for UsageInfo { pub struct UnpinHandleInner { /// Hash of the block pinned by this handle hash: Block::Hash, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, } impl Debug for UnpinHandleInner { @@ -291,7 +291,7 @@ impl UnpinHandleInner { /// Create a new [`UnpinHandleInner`] pub fn new( hash: Block::Hash, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> Self { Self { hash, unpin_worker_sender } } @@ -299,12 +299,25 @@ impl UnpinHandleInner { impl Drop for UnpinHandleInner { fn drop(&mut self) { - if let Err(err) = self.unpin_worker_sender.unbounded_send(self.hash) { + if let Err(err) = + self.unpin_worker_sender.unbounded_send(UnpinWorkerMessage::Unpin(self.hash)) + { log::debug!(target: "db", "Unable to unpin block with hash: {}, error: {:?}", self.hash, err); }; } } +/// Message that signals notification-based pinning actions to the pinning-worker. +/// +/// When the notification is dropped, an `Unpin` message should be sent to the worker. +#[derive(Debug)] +pub enum UnpinWorkerMessage { + /// Should be sent when a import or finality notification is created. + AnnouncePin(Block::Hash), + /// Should be sent when a import or finality notification is dropped. + Unpin(Block::Hash), +} + /// Keeps a specific block pinned while the handle is alive. /// Once the last handle instance for a given block is dropped, the /// block is unpinned in the [`Backend`](crate::backend::Backend::unpin_block). @@ -315,7 +328,7 @@ impl UnpinHandle { /// Create a new [`UnpinHandle`] pub fn new( hash: Block::Hash, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> UnpinHandle { UnpinHandle(Arc::new(UnpinHandleInner::new(hash, unpin_worker_sender))) } @@ -353,7 +366,7 @@ impl BlockImportNotification { header: Block::Header, is_new_best: bool, tree_route: Option>>, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> Self { Self { hash, @@ -412,7 +425,7 @@ impl FinalityNotification { /// Create finality notification from finality summary. pub fn from_summary( mut summary: FinalizeSummary, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> FinalityNotification { let hash = summary.finalized.pop().unwrap_or_default(); FinalityNotification { @@ -436,7 +449,7 @@ impl BlockImportNotification { /// Create finality notification from finality summary. pub fn from_summary( summary: ImportSummary, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, ) -> BlockImportNotification { let hash = summary.hash; BlockImportNotification { diff --git a/substrate/client/authority-discovery/Cargo.toml b/substrate/client/authority-discovery/Cargo.toml index 4c8370233e9413d7a2c6476e58ca61dfc8f3dce6..cdd4052f0b0998f283ede885f7e7de7509ecd169 100644 --- a/substrate/client/authority-discovery/Cargo.toml +++ b/substrate/client/authority-discovery/Cargo.toml @@ -29,10 +29,10 @@ multihash = { version = "0.18.1", default-features = false, features = [ "sha2", "std", ] } -log = "0.4.17" +log = { workspace = true, default-features = true } prost = "0.12" rand = "0.8.5" -thiserror = "1.0" +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-client-api = { path = "../api" } sc-network = { path = "../network" } diff --git a/substrate/client/basic-authorship/Cargo.toml b/substrate/client/basic-authorship/Cargo.toml index 370f4a4adf5c17f57e4c036a7aaeed81e559a1da..51a06464d0d6d5df0ccf960509f4df46d8b12207 100644 --- a/substrate/client/basic-authorship/Cargo.toml +++ b/substrate/client/basic-authorship/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-block-builder = { path = "../block-builder" } sc-proposer-metrics = { path = "../proposer-metrics" } diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index c07f3e639c3e15b572dbb470c1518b9ddfddae32..fdc3d4918de32c9263286a0b3bfe73a01d26538b 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -87,6 +87,22 @@ pub struct ProposerFactory { _phantom: PhantomData, } +impl Clone for ProposerFactory { + fn clone(&self) -> Self { + Self { + spawn_handle: self.spawn_handle.clone(), + client: self.client.clone(), + transaction_pool: self.transaction_pool.clone(), + metrics: self.metrics.clone(), + default_block_size_limit: self.default_block_size_limit, + soft_deadline_percent: self.soft_deadline_percent, + telemetry: self.telemetry.clone(), + include_proof_in_block_size_estimation: self.include_proof_in_block_size_estimation, + _phantom: self._phantom, + } + } +} + impl ProposerFactory { /// Create a new proposer factory. /// diff --git a/substrate/client/chain-spec/Cargo.toml b/substrate/client/chain-spec/Cargo.toml index 9ab12dc2ad55878714327e4c51c049c8d8da5396..f2138c07d71ad4c524e12d33ffb73d453d707fbb 100644 --- a/substrate/client/chain-spec/Cargo.toml +++ b/substrate/client/chain-spec/Cargo.toml @@ -18,8 +18,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } memmap2 = "0.9.3" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sc-client-api = { path = "../api" } sc-chain-spec-derive = { path = "derive" } sc-executor = { path = "../executor" } @@ -32,7 +32,7 @@ sp-crypto-hashing = { path = "../../primitives/crypto/hashing" } sp-genesis-builder = { path = "../../primitives/genesis-builder" } sp-runtime = { path = "../../primitives/runtime" } sp-state-machine = { path = "../../primitives/state-machine" } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } array-bytes = { version = "6.1" } docify = "0.2.7" diff --git a/substrate/client/chain-spec/derive/Cargo.toml b/substrate/client/chain-spec/derive/Cargo.toml index 5e8e64e55513350d001e69755816e68536ac4592..521eee578ecae3b03cf86a3b4e3630bb7cd22f02 100644 --- a/substrate/client/chain-spec/derive/Cargo.toml +++ b/substrate/client/chain-spec/derive/Cargo.toml @@ -20,5 +20,5 @@ proc-macro = true [dependencies] proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = "2.0.48" +quote = { workspace = true } +syn = { workspace = true } diff --git a/substrate/client/chain-spec/src/chain_spec.rs b/substrate/client/chain-spec/src/chain_spec.rs index fe8fcfda216e1fa86215daf93add2aa6adc78bd9..78e81e10d2b613d83c98e8b689cba2fd1356287c 100644 --- a/substrate/client/chain-spec/src/chain_spec.rs +++ b/substrate/client/chain-spec/src/chain_spec.rs @@ -1237,15 +1237,7 @@ mod tests { "TestName", "test", ChainType::Local, - move || substrate_test_runtime::RuntimeGenesisConfig { - babe: substrate_test_runtime::BabeConfig { - epoch_config: Some( - substrate_test_runtime::TEST_RUNTIME_BABE_EPOCH_CONFIGURATION, - ), - ..Default::default() - }, - ..Default::default() - }, + || Default::default(), Vec::new(), None, None, diff --git a/substrate/client/chain-spec/src/genesis_block.rs b/substrate/client/chain-spec/src/genesis_block.rs index 6aa156a620a7933034643f8a7bdbf8e0d617ad97..3c7b9f64dcd6bc0babff662747571f9d31853eec 100644 --- a/substrate/client/chain-spec/src/genesis_block.rs +++ b/substrate/client/chain-spec/src/genesis_block.rs @@ -18,23 +18,25 @@ //! Tool for creating the genesis block. -use std::{collections::hash_map::DefaultHasher, marker::PhantomData, sync::Arc}; +use std::{marker::PhantomData, sync::Arc}; +use codec::Encode; use sc_client_api::{backend::Backend, BlockImportOperation}; use sc_executor::RuntimeVersionOf; use sp_core::storage::{well_known_keys, StateVersion, Storage}; use sp_runtime::{ - traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}, + traits::{Block as BlockT, Hash as HashT, HashingFor, Header as HeaderT, Zero}, BuildStorage, }; /// Return the state version given the genesis storage and executor. -pub fn resolve_state_version_from_wasm( +pub fn resolve_state_version_from_wasm( storage: &Storage, executor: &E, ) -> sp_blockchain::Result where E: RuntimeVersionOf, + H: HashT, { if let Some(wasm) = storage.top.get(well_known_keys::CODE) { let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. @@ -43,12 +45,7 @@ where let runtime_code = sp_core::traits::RuntimeCode { code_fetcher: &code_fetcher, heap_pages: None, - hash: { - use std::hash::{Hash, Hasher}; - let mut state = DefaultHasher::new(); - wasm.hash(&mut state); - state.finish().to_le_bytes().to_vec() - }, + hash: ::hash(wasm).encode(), }; let runtime_version = RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; @@ -129,7 +126,8 @@ impl, E: RuntimeVersionOf> BuildGenesisBlock sp_blockchain::Result<(Block, Self::BlockImportOperation)> { let Self { genesis_storage, commit_genesis_state, backend, executor, _phantom } = self; - let genesis_state_version = resolve_state_version_from_wasm(&genesis_storage, &executor)?; + let genesis_state_version = + resolve_state_version_from_wasm::<_, HashingFor>(&genesis_storage, &executor)?; let mut op = backend.begin_operation()?; let state_root = op.set_genesis_state(genesis_storage, commit_genesis_state, genesis_state_version)?; diff --git a/substrate/client/chain-spec/src/genesis_config_builder.rs b/substrate/client/chain-spec/src/genesis_config_builder.rs index 8766dd5c5ad28c9263f7538d255e03a898244026..6b956316203cc1279485fa1c5ad39cc0885cc14d 100644 --- a/substrate/client/chain-spec/src/genesis_config_builder.rs +++ b/substrate/client/chain-spec/src/genesis_config_builder.rs @@ -149,7 +149,7 @@ mod tests { ::new(substrate_test_runtime::wasm_binary_unwrap()) .get_default_config() .unwrap(); - let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":null},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#; + let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": []}, "substrateTest": {"authorities": []}, "system": {}}"#; assert_eq!(from_str::(expected).unwrap(), config); } diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index 2d9c2fa5ffbdd97e53bb2a246ceee69c013ea7c7..0582018283c6e49882c71106d36f73d6ba7cedb2 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -18,20 +18,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" chrono = "0.4.31" -clap = { version = "4.4.18", features = ["derive", "string", "wrap_help"] } +clap = { version = "4.5.1", features = ["derive", "string", "wrap_help"] } fdlimit = "0.3.0" futures = "0.3.21" itertools = "0.10.3" libp2p-identity = { version = "0.1.3", features = ["ed25519", "peerid"] } -log = "0.4.17" +log = { workspace = true, default-features = true } names = { version = "0.14.0", default-features = false } parity-scale-codec = "3.6.1" rand = "0.8.5" regex = "1.6.0" rpassword = "7.0.0" -serde = "1.0.195" -serde_json = "1.0.111" -thiserror = "1.0.48" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } bip39 = "2.0.0" tokio = { version = "1.22.0", features = ["parking_lot", "rt-multi-thread", "signal"] } sc-client-api = { path = "../api" } diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs index f7b0fc51049106306ccff6d0d8fedc4db48868a6..221c32affd5a91d680f23ed11eb5f7a9a4051da2 100644 --- a/substrate/client/cli/src/commands/run_cmd.rs +++ b/substrate/client/cli/src/commands/run_cmd.rs @@ -30,11 +30,14 @@ use crate::{ use clap::Parser; use regex::Regex; use sc_service::{ - config::{BasePath, PrometheusConfig, TransactionPoolOptions}, + config::{BasePath, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions}, ChainSpec, Role, }; use sc_telemetry::TelemetryEndpoints; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + num::NonZeroU32, +}; /// The `run` command used to run a node. #[derive(Debug, Clone, Parser)] @@ -59,7 +62,7 @@ pub struct RunCmd { /// Not all RPC methods are safe to be exposed publicly. /// /// Use an RPC proxy server to filter out dangerous methods. More details: - /// . + /// . /// /// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks. #[arg(long)] @@ -82,6 +85,15 @@ pub struct RunCmd { )] pub rpc_methods: RpcMethods, + /// RPC rate limiting (calls/minute) for each connection. + /// + /// This is disabled by default. + /// + /// For example `--rpc-rate-limit 10` will maximum allow + /// 10 calls per minute per connection. + #[arg(long)] + pub rpc_rate_limit: Option, + /// Set the maximum RPC request payload size for both HTTP and WS in megabytes. #[arg(long, default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB)] pub rpc_max_request_size: u32, @@ -113,6 +125,14 @@ pub struct RunCmd { #[arg(long, default_value_t = RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)] pub rpc_message_buffer_capacity_per_connection: u32, + /// Disable RPC batch requests + #[arg(long, alias = "rpc_no_batch_requests", conflicts_with_all = &["rpc_max_batch_request_len"])] + pub rpc_disable_batch_requests: bool, + + /// Limit the max length per RPC batch request + #[arg(long, conflicts_with_all = &["rpc_disable_batch_requests"], value_name = "LEN")] + pub rpc_max_batch_request_len: Option, + /// Specify browser *origins* allowed to access the HTTP & WS RPC servers. /// /// A comma-separated list of origins (protocol://domain or special `null` @@ -399,6 +419,26 @@ impl CliConfiguration for RunCmd { Ok(self.rpc_max_subscriptions_per_connection) } + fn rpc_buffer_capacity_per_connection(&self) -> Result { + Ok(self.rpc_message_buffer_capacity_per_connection) + } + + fn rpc_batch_config(&self) -> Result { + let cfg = if self.rpc_disable_batch_requests { + RpcBatchRequestConfig::Disabled + } else if let Some(l) = self.rpc_max_batch_request_len { + RpcBatchRequestConfig::Limit(l) + } else { + RpcBatchRequestConfig::Unlimited + }; + + Ok(cfg) + } + + fn rpc_rate_limit(&self) -> Result> { + Ok(self.rpc_rate_limit) + } + fn transaction_pool(&self, is_dev: bool) -> Result { Ok(self.pool_config.transaction_pool(is_dev)) } diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index defcc4a8a69078513ffacf667e6de7a7dd67f6dd..5def9ce9b72620eb7942ac6ee68b16493f6b8053 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -28,12 +28,13 @@ use sc_service::{ config::{ BasePath, Configuration, DatabaseSource, KeystoreConfig, NetworkConfiguration, NodeKeyConfig, OffchainWorkerConfig, OutputFormat, PrometheusConfig, PruningMode, Role, - RpcMethods, TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod, + RpcBatchRequestConfig, RpcMethods, TelemetryEndpoints, TransactionPoolOptions, + WasmExecutionMethod, }, BlocksPruning, ChainSpec, TracingReceiver, }; use sc_tracing::logging::LoggerBuilder; -use std::{net::SocketAddr, path::PathBuf}; +use std::{net::SocketAddr, num::NonZeroU32, path::PathBuf}; /// The maximum number of characters for a node name. pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64; @@ -338,6 +339,16 @@ pub trait CliConfiguration: Sized { Ok(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN) } + /// RPC server batch request configuration. + fn rpc_batch_config(&self) -> Result { + Ok(RpcBatchRequestConfig::Unlimited) + } + + /// RPC rate limit configuration. + fn rpc_rate_limit(&self) -> Result> { + Ok(None) + } + /// Get the prometheus configuration (`None` if disabled) /// /// By default this is `None`. @@ -510,6 +521,8 @@ pub trait CliConfiguration: Sized { rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?, rpc_port: DCV::rpc_listen_port(), rpc_message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?, + rpc_batch_config: self.rpc_batch_config()?, + rpc_rate_limit: self.rpc_rate_limit()?, prometheus_config: self .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, telemetry_endpoints, diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs index e37c8ab0e55163f72a4ec79dbc3c8c221205ac84..4201a0f4062fb0063621d85dd02feb0deade24bc 100644 --- a/substrate/client/cli/src/runner.rs +++ b/substrate/client/cli/src/runner.rs @@ -271,6 +271,8 @@ mod tests { rpc_max_subs_per_conn: Default::default(), rpc_message_buffer_capacity: Default::default(), rpc_port: 9944, + rpc_batch_config: sc_service::config::RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/client/consensus/aura/Cargo.toml b/substrate/client/consensus/aura/Cargo.toml index 33f7d160d8131f2b7eaadd679c8f32c112c1646f..213f75974da0c99cc5c39248a8a789c4989d8047 100644 --- a/substrate/client/consensus/aura/Cargo.toml +++ b/substrate/client/consensus/aura/Cargo.toml @@ -19,8 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -log = "0.4.17" -thiserror = "1.0" +log = { workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-block-builder = { path = "../../block-builder" } sc-client-api = { path = "../../api" } diff --git a/substrate/client/consensus/babe/Cargo.toml b/substrate/client/consensus/babe/Cargo.toml index 01c5d062d61db800bc2fbdc727aa6349963505a8..c98fb7112b7c25bd39d980cab20c188f30b42eff 100644 --- a/substrate/client/consensus/babe/Cargo.toml +++ b/substrate/client/consensus/babe/Cargo.toml @@ -20,12 +20,12 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } num-bigint = "0.4.3" num-rational = "0.4.1" num-traits = "0.2.17" parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } fork-tree = { path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } diff --git a/substrate/client/consensus/babe/rpc/Cargo.toml b/substrate/client/consensus/babe/rpc/Cargo.toml index 2ca029444d078754bdd954386d2104b02cc09e34..043b566673e7bd5aa581e3043dfa1280aa04d53f 100644 --- a/substrate/client/consensus/babe/rpc/Cargo.toml +++ b/substrate/client/consensus/babe/rpc/Cargo.toml @@ -16,10 +16,10 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } futures = "0.3.21" -serde = { version = "1.0.195", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sc-consensus-babe = { path = ".." } sc-consensus-epochs = { path = "../../epochs" } sc-rpc-api = { path = "../../../rpc-api" } @@ -33,7 +33,7 @@ sp-keystore = { path = "../../../../primitives/keystore" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } tokio = "1.22.0" sc-consensus = { path = "../../common" } sc-keystore = { path = "../../../keystore" } diff --git a/substrate/client/consensus/babe/rpc/src/lib.rs b/substrate/client/consensus/babe/rpc/src/lib.rs index 307b1f955ba2efa3a51302225d3d74e00e800538..a3e811baecffd6de1b9f94952be40d00d8b61de3 100644 --- a/substrate/client/consensus/babe/rpc/src/lib.rs +++ b/substrate/client/consensus/babe/rpc/src/lib.rs @@ -258,9 +258,9 @@ mod tests { let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; let (response, _) = api.raw_json_request(request, 1).await.unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#; + let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[],"secondary_vrf":[1,2,4]}},"id":1}"#; - assert_eq!(&response.result, expected); + assert_eq!(response, expected); } #[tokio::test] @@ -272,6 +272,6 @@ mod tests { let (response, _) = api.raw_json_request(request, 1).await.unwrap(); let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#; - assert_eq!(&response.result, expected); + assert_eq!(response, expected); } } diff --git a/substrate/client/consensus/beefy/Cargo.toml b/substrate/client/consensus/beefy/Cargo.toml index 3522697427875034ec330d31b80b22085c1f042b..8552a49002239aaeaf232c0aebf4180cec207337 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -18,9 +18,9 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } fnv = "1.0.6" futures = "0.3" -log = "0.4" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } wasm-timer = "0.2.5" prometheus = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } @@ -44,7 +44,7 @@ tokio = "1.22.0" [dev-dependencies] -serde = "1.0.195" +serde = { workspace = true, default-features = true } tempfile = "3.1.0" sc-block-builder = { path = "../../block-builder" } sc-network-test = { path = "../../network/test" } @@ -52,3 +52,12 @@ sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa" } sp-keyring = { path = "../../../primitives/keyring" } sp-tracing = { path = "../../../primitives/tracing" } substrate-test-runtime-client = { path = "../../../test-utils/runtime/client" } + +[features] +# This feature adds BLS crypto primitives. It should not be used in production since +# the BLS implementation and interface may still be subject to significant change. +bls-experimental = [ + "sp-application-crypto/bls-experimental", + "sp-consensus-beefy/bls-experimental", + "sp-core/bls-experimental", +] diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml index 496aefac113aeffbdc9ad361c2df9a8fc2c99856..bb2ae4a08966707fd93afd48b625ccc4f760762b 100644 --- a/substrate/client/consensus/beefy/rpc/Cargo.toml +++ b/substrate/client/consensus/beefy/rpc/Cargo.toml @@ -14,11 +14,11 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } -log = "0.4" +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } +log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde = { version = "1.0.195", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sc-consensus-beefy = { path = ".." } sp-consensus-beefy = { path = "../../../../primitives/consensus/beefy" } sc-rpc = { path = "../../../rpc" } @@ -26,7 +26,7 @@ sp-core = { path = "../../../../primitives/core" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } sc-rpc = { path = "../../../rpc", features = ["test-helpers"] } substrate-test-runtime-client = { path = "../../../../test-utils/runtime/client" } tokio = { version = "1.22.0", features = ["macros"] } diff --git a/substrate/client/consensus/beefy/rpc/src/lib.rs b/substrate/client/consensus/beefy/rpc/src/lib.rs index 03c83e92716c7d860760af40a578f651b962d23d..f01baee2d6ece9a9d1dd36e1524a1d004e9b0401 100644 --- a/substrate/client/consensus/beefy/rpc/src/lib.rs +++ b/substrate/client/consensus/beefy/rpc/src/lib.rs @@ -184,10 +184,10 @@ mod tests { async fn uninitialized_rpc_handler() { let (rpc, _) = setup_io_handler(); let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; - let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#.to_string(); + let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); - assert_eq!(expected_response, response.result); + assert_eq!(expected_response, response); } #[tokio::test] @@ -205,20 +205,18 @@ mod tests { \"jsonrpc\":\"2.0\",\ \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\",\ \"id\":1\ - }" - .to_string(); + }"; let not_ready = "{\ \"jsonrpc\":\"2.0\",\ \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"},\ \"id\":1\ - }" - .to_string(); + }"; let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); while std::time::Instant::now() < deadline { let (response, _) = io.raw_json_request(request, 1).await.expect("RPC requests work"); - if response.result != not_ready { - assert_eq!(response.result, expected); + if response != not_ready { + assert_eq!(response, expected); // Success return } @@ -249,7 +247,7 @@ mod tests { .unwrap(); let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - assert_eq!(response.result, expected); + assert_eq!(response, expected); } fn create_finality_proof() -> BeefyVersionedFinalityProof { diff --git a/substrate/client/consensus/beefy/src/aux_schema.rs b/substrate/client/consensus/beefy/src/aux_schema.rs index 944a00f8372fa8b0643dd4ddff8f8ce684e9f325..534f668ae69c2996064bef086e2958b23f48caf0 100644 --- a/substrate/client/consensus/beefy/src/aux_schema.rs +++ b/substrate/client/consensus/beefy/src/aux_schema.rs @@ -20,7 +20,7 @@ use crate::{error::Error, worker::PersistedState, LOG_TARGET}; use codec::{Decode, Encode}; -use log::{info, trace}; +use log::{debug, trace}; use sc_client_api::{backend::AuxStore, Backend}; use sp_runtime::traits::Block as BlockT; @@ -30,7 +30,7 @@ const WORKER_STATE_KEY: &[u8] = b"beefy_voter_state"; const CURRENT_VERSION: u32 = 4; pub(crate) fn write_current_version(backend: &BE) -> Result<(), Error> { - info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION); + debug!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION); AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[]) .map_err(|e| Error::Backend(e.to_string())) } diff --git a/substrate/client/consensus/beefy/src/communication/gossip.rs b/substrate/client/consensus/beefy/src/communication/gossip.rs index 645a10b2a1d43f9bb879a799ac9625b2f623506c..eb43c9173d751bf75bc4973aca248d80fb68ca48 100644 --- a/substrate/client/consensus/beefy/src/communication/gossip.rs +++ b/substrate/client/consensus/beefy/src/communication/gossip.rs @@ -56,6 +56,8 @@ pub(super) enum Action { Keep(H, ReputationChange), // discard, applying cost/benefit to originator. Discard(ReputationChange), + // ignore, no cost/benefit applied to originator. + DiscardNoReport, } /// An outcome of examining a message. @@ -68,7 +70,7 @@ enum Consider { /// Message is from the future. Reject. RejectFuture, /// Message cannot be evaluated. Reject. - RejectOutOfScope, + CannotEvaluate, } /// BEEFY gossip message type that gets encoded and sent on the network. @@ -168,18 +170,14 @@ impl Filter { .as_ref() .map(|f| // only from current set and only [filter.start, filter.end] - if set_id < f.validator_set.id() { + if set_id < f.validator_set.id() || round < f.start { Consider::RejectPast - } else if set_id > f.validator_set.id() { - Consider::RejectFuture - } else if round < f.start { - Consider::RejectPast - } else if round > f.end { + } else if set_id > f.validator_set.id() || round > f.end { Consider::RejectFuture } else { Consider::Accept }) - .unwrap_or(Consider::RejectOutOfScope) + .unwrap_or(Consider::CannotEvaluate) } /// Return true if `round` is >= than `max(session_start, best_beefy)`, @@ -199,7 +197,7 @@ impl Filter { Consider::Accept } ) - .unwrap_or(Consider::RejectOutOfScope) + .unwrap_or(Consider::CannotEvaluate) } /// Add new _known_ `round` to the set of seen valid justifications. @@ -244,7 +242,7 @@ where pub(crate) fn new( known_peers: Arc>>, ) -> (GossipValidator, TracingUnboundedReceiver) { - let (tx, rx) = tracing_unbounded("mpsc_beefy_gossip_validator", 10_000); + let (tx, rx) = tracing_unbounded("mpsc_beefy_gossip_validator", 100_000); let val = GossipValidator { votes_topic: votes_topic::(), justifs_topic: proofs_topic::(), @@ -289,7 +287,9 @@ where match filter.consider_vote(round, set_id) { Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE), Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE), - Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE), + // When we can't evaluate, it's our fault (e.g. filter not initialized yet), we + // discard the vote without punishing or rewarding the sending peer. + Consider::CannotEvaluate => return Action::DiscardNoReport, Consider::Accept => {}, } @@ -330,7 +330,9 @@ where match guard.consider_finality_proof(round, set_id) { Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE), Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE), - Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE), + // When we can't evaluate, it's our fault (e.g. filter not initialized yet), we + // discard the proof without punishing or rewarding the sending peer. + Consider::CannotEvaluate => return Action::DiscardNoReport, Consider::Accept => {}, } @@ -357,7 +359,9 @@ where Action::Keep(self.justifs_topic, benefit::VALIDATED_PROOF) } }) - .unwrap_or(Action::Discard(cost::OUT_OF_SCOPE_MESSAGE)) + // When we can't evaluate, it's our fault (e.g. filter not initialized yet), we + // discard the proof without punishing or rewarding the sending peer. + .unwrap_or(Action::DiscardNoReport) }; if matches!(action, Action::Keep(_, _)) { self.gossip_filter.write().mark_round_as_proven(round); @@ -404,6 +408,7 @@ where self.report(*sender, cb); ValidationResult::Discard }, + Action::DiscardNoReport => ValidationResult::Discard, } } @@ -485,8 +490,8 @@ pub(crate) mod tests { use sc_network_test::Block; use sp_application_crypto::key_types::BEEFY as BEEFY_KEY_TYPE; use sp_consensus_beefy::{ - ecdsa_crypto::Signature, known_payloads, Commitment, Keyring, MmrRootHash, Payload, - SignedCommitment, VoteMessage, + ecdsa_crypto::Signature, known_payloads, test_utils::Keyring, Commitment, MmrRootHash, + Payload, SignedCommitment, VoteMessage, }; use sp_keystore::{testing::MemoryKeystore, Keystore}; @@ -507,10 +512,13 @@ pub(crate) mod tests { } } - pub fn sign_commitment(who: &Keyring, commitment: &Commitment) -> Signature { + pub fn sign_commitment( + who: &Keyring, + commitment: &Commitment, + ) -> Signature { let store = MemoryKeystore::new(); store.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&who.to_seed())).unwrap(); - let beefy_keystore: BeefyKeystore = Some(store.into()).into(); + let beefy_keystore: BeefyKeystore = Some(store.into()).into(); beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap() } @@ -538,7 +546,10 @@ pub(crate) mod tests { .validators() .iter() .map(|validator: &AuthorityId| { - Some(sign_commitment(&Keyring::from_public(validator).unwrap(), &commitment)) + Some(sign_commitment( + &Keyring::::from_public(validator).unwrap(), + &commitment, + )) }) .collect(); @@ -547,7 +558,7 @@ pub(crate) mod tests { #[test] fn should_validate_messages() { - let keys = vec![Keyring::Alice.public()]; + let keys = vec![Keyring::::Alice.public()]; let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap(); let (gv, mut report_stream) = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); @@ -573,8 +584,8 @@ pub(crate) mod tests { // filter not initialized let res = gv.validate(&mut context, &sender, &encoded); assert!(matches!(res, ValidationResult::Discard)); - expected_report.cost_benefit = cost::OUT_OF_SCOPE_MESSAGE; - assert_eq!(report_stream.try_recv().unwrap(), expected_report); + // nothing reported + assert!(report_stream.try_recv().is_err()); gv.update_filter(GossipFilterCfg { start: 0, end: 10, validator_set: &validator_set }); // nothing in cache first time diff --git a/substrate/client/consensus/beefy/src/communication/mod.rs b/substrate/client/consensus/beefy/src/communication/mod.rs index 3827559057dde856d201ebb9c9ff71a1d7d11f47..6fda63688e6952839c18e1bbc9ff50ff0c5f7c21 100644 --- a/substrate/client/consensus/beefy/src/communication/mod.rs +++ b/substrate/client/consensus/beefy/src/communication/mod.rs @@ -90,8 +90,6 @@ mod cost { pub(super) const BAD_SIGNATURE: Rep = Rep::new(-100, "BEEFY: Bad signature"); // Message received with vote from voter not in validator set. pub(super) const UNKNOWN_VOTER: Rep = Rep::new(-150, "BEEFY: Unknown voter"); - // A message received that cannot be evaluated relative to our current state. - pub(super) const OUT_OF_SCOPE_MESSAGE: Rep = Rep::new(-500, "BEEFY: Out-of-scope message"); // Message containing invalid proof. pub(super) const INVALID_PROOF: Rep = Rep::new(-5000, "BEEFY: Invalid commit"); // Reputation cost per signature checked for invalid proof. diff --git a/substrate/client/consensus/beefy/src/import.rs b/substrate/client/consensus/beefy/src/import.rs index fc19ecc301422d74d5da5a289ed641810b1022f1..ed8ed68c4e8d0d378728ba87d1ffe726f6c4c11a 100644 --- a/substrate/client/consensus/beefy/src/import.rs +++ b/substrate/client/consensus/beefy/src/import.rs @@ -159,7 +159,7 @@ where // The proof is valid and the block is imported and final, we can import. debug!( target: LOG_TARGET, - "🥩 import justif {:?} for block number {:?}.", proof, number + "🥩 import justif {} for block number {:?}.", proof, number ); // Send the justification to the BEEFY voter for processing. self.justification_sender diff --git a/substrate/client/consensus/beefy/src/justification.rs b/substrate/client/consensus/beefy/src/justification.rs index 483184e2374a2b9239554e4ce2709cd7a294cb8c..7f1b9e5237c3970eb8cba4936debd18c77117288 100644 --- a/substrate/client/consensus/beefy/src/justification.rs +++ b/substrate/client/consensus/beefy/src/justification.rs @@ -76,7 +76,7 @@ pub(crate) fn verify_with_validator_set( .as_ref() .map(|sig| { signatures_checked += 1; - BeefyKeystore::verify(id, sig, &message[..]) + BeefyKeystore::verify(*id, sig, &message[..]) }) .unwrap_or(false) }) @@ -93,7 +93,8 @@ pub(crate) fn verify_with_validator_set( #[cfg(test)] pub(crate) mod tests { use sp_consensus_beefy::{ - known_payloads, Commitment, Keyring, Payload, SignedCommitment, VersionedFinalityProof, + known_payloads, test_utils::Keyring, Commitment, Payload, SignedCommitment, + VersionedFinalityProof, }; use substrate_test_runtime_client::runtime::Block; @@ -103,7 +104,7 @@ pub(crate) mod tests { pub(crate) fn new_finality_proof( block_num: NumberFor, validator_set: &ValidatorSet, - keys: &[Keyring], + keys: &[Keyring], ) -> BeefyVersionedFinalityProof { let commitment = Commitment { payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]), @@ -174,7 +175,7 @@ pub(crate) mod tests { }; // change a signature to a different key *bad_signed_commitment.signatures.first_mut().unwrap() = - Some(Keyring::Dave.sign(&bad_signed_commitment.commitment.encode())); + Some(Keyring::::Dave.sign(&bad_signed_commitment.commitment.encode())); match verify_with_validator_set::(block_num, &validator_set, &bad_proof.into()) { Err((ConsensusError::InvalidJustification, 3)) => (), e => assert!(false, "Got unexpected {:?}", e), diff --git a/substrate/client/consensus/beefy/src/keystore.rs b/substrate/client/consensus/beefy/src/keystore.rs index 75c44de3324cee7b4f0cc4a08335640708952157..2ddc938fbc6ce33aa74c62d051cf77a519c634ee 100644 --- a/substrate/client/consensus/beefy/src/keystore.rs +++ b/substrate/client/consensus/beefy/src/keystore.rs @@ -16,41 +16,45 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, RuntimeAppPublic}; +use sp_application_crypto::{key_types::BEEFY as BEEFY_KEY_TYPE, AppCrypto, RuntimeAppPublic}; +use sp_consensus_beefy::{AuthorityIdBound, BeefyAuthorityId, BeefySignatureHasher}; +use sp_core::ecdsa; +#[cfg(feature = "bls-experimental")] +use sp_core::ecdsa_bls377; use sp_crypto_hashing::keccak_256; use sp_keystore::KeystorePtr; +use codec::Decode; use log::warn; - -use sp_consensus_beefy::{ - ecdsa_crypto::{Public, Signature}, - BeefyAuthorityId, -}; +use std::marker::PhantomData; use crate::{error, LOG_TARGET}; -/// Hasher used for BEEFY signatures. -pub(crate) type BeefySignatureHasher = sp_runtime::traits::Keccak256; - /// A BEEFY specific keystore implemented as a `Newtype`. This is basically a /// wrapper around [`sp_keystore::Keystore`] and allows to customize /// common cryptographic functionality. -pub(crate) struct BeefyKeystore(Option); +pub(crate) struct BeefyKeystore( + Option, + PhantomData AuthorityId>, +); -impl BeefyKeystore { +impl BeefyKeystore { /// Check if the keystore contains a private key for one of the public keys /// contained in `keys`. A public key with a matching private key is known /// as a local authority id. /// /// Return the public key for which we also do have a private key. If no /// matching private key is found, `None` will be returned. - pub fn authority_id(&self, keys: &[Public]) -> Option { + pub fn authority_id(&self, keys: &[AuthorityId]) -> Option { let store = self.0.clone()?; // we do check for multiple private keys as a key store sanity check. - let public: Vec = keys + let public: Vec = keys .iter() - .filter(|k| store.has_keys(&[(k.to_raw_vec(), BEEFY_KEY_TYPE)])) + .filter(|k| { + store + .has_keys(&[(::to_raw_vec(k), BEEFY_KEY_TYPE)]) + }) .cloned() .collect(); @@ -71,55 +75,125 @@ impl BeefyKeystore { /// Note that `message` usually will be pre-hashed before being signed. /// /// Return the message signature or an error in case of failure. - pub fn sign(&self, public: &Public, message: &[u8]) -> Result { + pub fn sign( + &self, + public: &AuthorityId, + message: &[u8], + ) -> Result<::Signature, error::Error> { let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - let msg = keccak_256(message); - let public = public.as_ref(); - - let sig = store - .ecdsa_sign_prehashed(BEEFY_KEY_TYPE, public, &msg) - .map_err(|e| error::Error::Keystore(e.to_string()))? - .ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?; - - // check that `sig` has the expected result type - let sig = sig.clone().try_into().map_err(|_| { - error::Error::Signature(format!("invalid signature {:?} for key {:?}", sig, public)) + // ECDSA should use ecdsa_sign_prehashed since it needs to be hashed by keccak_256 instead + // of blake2. As such we need to deal with producing the signatures case-by-case + let signature_byte_array: Vec = match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => { + let msg_hash = keccak_256(message); + let public: ecdsa::Public = ecdsa::Public::try_from(public.as_slice()).unwrap(); + + let sig = store + .ecdsa_sign_prehashed(BEEFY_KEY_TYPE, &public, &msg_hash) + .map_err(|e| error::Error::Keystore(e.to_string()))? + .ok_or_else(|| { + error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()) + })?; + let sig_ref: &[u8] = sig.as_ref(); + sig_ref.to_vec() + }, + + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => { + let public: ecdsa_bls377::Public = + ecdsa_bls377::Public::try_from(public.as_slice()).unwrap(); + let sig = store + .ecdsa_bls377_sign_with_keccak256(BEEFY_KEY_TYPE, &public, &message) + .map_err(|e| error::Error::Keystore(e.to_string()))? + .ok_or_else(|| error::Error::Signature("bls377_sign() failed".to_string()))?; + let sig_ref: &[u8] = sig.as_ref(); + sig_ref.to_vec() + }, + + _ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into()))?, + }; + + //check that `sig` has the expected result type + let signature = ::Signature::decode( + &mut signature_byte_array.as_slice(), + ) + .map_err(|_| { + error::Error::Signature(format!( + "invalid signature {:?} for key {:?}", + signature_byte_array, public + )) })?; - Ok(sig) + Ok(signature) } /// Returns a vector of [`sp_consensus_beefy::crypto::Public`] keys which are currently /// supported (i.e. found in the keystore). - pub fn public_keys(&self) -> Result, error::Error> { + pub fn public_keys(&self) -> Result, error::Error> { let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - let pk: Vec = - store.ecdsa_public_keys(BEEFY_KEY_TYPE).drain(..).map(Public::from).collect(); - - Ok(pk) + let pk = match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => store + .ecdsa_public_keys(BEEFY_KEY_TYPE) + .drain(..) + .map(|pk| AuthorityId::try_from(pk.as_ref())) + .collect::, _>>() + .or_else(|_| { + Err(error::Error::Keystore( + "unable to convert public key into authority id".into(), + )) + }), + + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => store + .ecdsa_bls377_public_keys(BEEFY_KEY_TYPE) + .drain(..) + .map(|pk| AuthorityId::try_from(pk.as_ref())) + .collect::, _>>() + .or_else(|_| { + Err(error::Error::Keystore( + "unable to convert public key into authority id".into(), + )) + }), + + _ => Err(error::Error::Keystore("key type is not supported by BEEFY Keystore".into())), + }; + + pk } /// Use the `public` key to verify that `sig` is a valid signature for `message`. /// /// Return `true` if the signature is authentic, `false` otherwise. - pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool { + pub fn verify( + public: &AuthorityId, + sig: &::Signature, + message: &[u8], + ) -> bool { BeefyAuthorityId::::verify(public, sig, message) } } -impl From> for BeefyKeystore { - fn from(store: Option) -> BeefyKeystore { - BeefyKeystore(store) +impl From> for BeefyKeystore +where + ::Signature: Send + Sync, +{ + fn from(store: Option) -> BeefyKeystore { + BeefyKeystore(store, PhantomData) } } #[cfg(test)] pub mod tests { - use sp_consensus_beefy::{ecdsa_crypto, Keyring}; - use sp_core::{ecdsa, Pair}; - use sp_keystore::testing::MemoryKeystore; + #[cfg(feature = "bls-experimental")] + use sp_consensus_beefy::ecdsa_bls_crypto; + use sp_consensus_beefy::{ + ecdsa_crypto, + test_utils::{BeefySignerAuthority, Keyring}, + }; + use sp_core::Pair as PairT; + use sp_keystore::{testing::MemoryKeystore, Keystore}; use super::*; use crate::error::Error; @@ -128,152 +202,265 @@ pub mod tests { MemoryKeystore::new().into() } - #[test] - fn verify_should_work() { - let msg = keccak_256(b"I am Alice!"); - let sig = Keyring::Alice.sign(b"I am Alice!"); - - assert!(ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Alice.public().into(), + fn pair_verify_should_work< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { + let msg = b"I am Alice!"; + let sig = Keyring::::Alice.sign(b"I am Alice!"); + + assert!(>::verify( + &Keyring::Alice.public(), + &sig, + &msg.as_slice(), )); // different public key -> fail - assert!(!ecdsa::Pair::verify_prehashed( - &sig.clone().into(), - &msg, - &Keyring::Bob.public().into(), + assert!(!>::verify( + &Keyring::Bob.public(), + &sig, + &msg.as_slice(), )); - let msg = keccak_256(b"I am not Alice!"); + let msg = b"I am not Alice!"; // different msg -> fail - assert!( - !ecdsa::Pair::verify_prehashed(&sig.into(), &msg, &Keyring::Alice.public().into(),) - ); + assert!(!>::verify( + &Keyring::Alice.public(), + &sig, + &msg.as_slice(), + )); + } + + /// Generate key pair in the given store using the provided seed + fn generate_in_store( + store: KeystorePtr, + key_type: sp_application_crypto::KeyTypeId, + owner: Option>, + ) -> AuthorityId + where + AuthorityId: + AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + ::Pair: BeefySignerAuthority, + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + { + let optional_seed: Option = owner.map(|owner| owner.to_seed()); + + match ::CRYPTO_ID { + ecdsa::CRYPTO_ID => { + let pk = store.ecdsa_generate_new(key_type, optional_seed.as_deref()).ok().unwrap(); + AuthorityId::decode(&mut pk.as_ref()).unwrap() + }, + #[cfg(feature = "bls-experimental")] + ecdsa_bls377::CRYPTO_ID => { + let pk = store + .ecdsa_bls377_generate_new(key_type, optional_seed.as_deref()) + .ok() + .unwrap(); + AuthorityId::decode(&mut pk.as_ref()).unwrap() + }, + _ => panic!("Requested CRYPTO_ID is not supported by the BEEFY Keyring"), + } } #[test] - fn pair_works() { - let want = ecdsa_crypto::Pair::from_string("//Alice", None) + fn pair_verify_should_work_ecdsa() { + pair_verify_should_work::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn pair_verify_should_work_ecdsa_n_bls() { + pair_verify_should_work::(); + } + + fn pair_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { + let want = ::Pair::from_string("//Alice", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Alice.pair().to_raw_vec(); + let got = Keyring::::Alice.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Bob", None) + let want = ::Pair::from_string("//Bob", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Bob.pair().to_raw_vec(); + let got = Keyring::::Bob.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Charlie", None) + let want = ::Pair::from_string("//Charlie", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Charlie.pair().to_raw_vec(); + let got = Keyring::::Charlie.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Dave", None) + let want = ::Pair::from_string("//Dave", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Dave.pair().to_raw_vec(); + let got = Keyring::::Dave.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Eve", None) + let want = ::Pair::from_string("//Eve", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Eve.pair().to_raw_vec(); + let got = Keyring::::Eve.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Ferdie", None) + let want = ::Pair::from_string("//Ferdie", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Ferdie.pair().to_raw_vec(); + let got = Keyring::::Ferdie.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//One", None) + let want = ::Pair::from_string("//One", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::One.pair().to_raw_vec(); + let got = Keyring::::One.pair().to_raw_vec(); assert_eq!(want, got); - let want = ecdsa_crypto::Pair::from_string("//Two", None) + let want = ::Pair::from_string("//Two", None) .expect("Pair failed") .to_raw_vec(); - let got = Keyring::Two.pair().to_raw_vec(); + let got = Keyring::::Two.pair().to_raw_vec(); assert_eq!(want, got); } #[test] - fn authority_id_works() { + fn ecdsa_pair_works() { + pair_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn ecdsa_n_bls_pair_works() { + pair_works::(); + } + + fn authority_id_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); + + let alice = Keyring::::Alice.public(); let bob = Keyring::Bob.public(); let charlie = Keyring::Charlie.public(); - let store: BeefyKeystore = Some(store).into(); + let beefy_store: BeefyKeystore = Some(store).into(); let mut keys = vec![bob, charlie]; - let id = store.authority_id(keys.as_slice()); + let id = beefy_store.authority_id(keys.as_slice()); assert!(id.is_none()); keys.push(alice.clone()); - let id = store.authority_id(keys.as_slice()).unwrap(); + let id = beefy_store.authority_id(keys.as_slice()).unwrap(); assert_eq!(id, alice); } #[test] - fn sign_works() { + fn authority_id_works_for_ecdsa() { + authority_id_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn authority_id_works_for_ecdsa_n_bls() { + authority_id_works::(); + } + + fn sign_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); - let store: BeefyKeystore = Some(store).into(); + let alice = Keyring::Alice.public(); + + let store: BeefyKeystore = Some(store).into(); let msg = b"are you involved or commited?"; let sig1 = store.sign(&alice, msg).unwrap(); - let sig2 = Keyring::Alice.sign(msg); + let sig2 = Keyring::::Alice.sign(msg); assert_eq!(sig1, sig2); } #[test] - fn sign_error() { + fn sign_works_for_ecdsa() { + sign_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn sign_works_for_ecdsa_n_bls() { + sign_works::(); + } + + fn sign_error< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >( + expected_error_message: &str, + ) where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Bob.to_seed())) - .ok() - .unwrap(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Bob)); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); let alice = Keyring::Alice.public(); let msg = b"are you involved or commited?"; let sig = store.sign(&alice, msg).err().unwrap(); - let err = Error::Signature("ecdsa_sign_prehashed() failed".to_string()); + let err = Error::Signature(expected_error_message.to_string()); assert_eq!(sig, err); } + #[test] + fn sign_error_for_ecdsa() { + sign_error::("ecdsa_sign_prehashed() failed"); + } + + #[cfg(feature = "bls-experimental")] + #[test] + fn sign_error_for_ecdsa_n_bls() { + sign_error::("bls377_sign() failed"); + } + #[test] fn sign_no_keystore() { - let store: BeefyKeystore = None.into(); + let store: BeefyKeystore = None.into(); let alice = Keyring::Alice.public(); let msg = b"are you involved or commited"; @@ -283,17 +470,21 @@ pub mod tests { assert_eq!(sig, err); } - #[test] - fn verify_works() { + fn verify_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { let store = keystore(); - let alice: ecdsa_crypto::Public = store - .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Alice)); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); + + let alice = Keyring::Alice.public(); // `msg` and `sig` match let msg = b"are you involved or commited?"; @@ -305,32 +496,48 @@ pub mod tests { assert!(!BeefyKeystore::verify(&alice, &sig, msg)); } - // Note that we use keys with and without a seed for this test. #[test] - fn public_keys_works() { + fn verify_works_for_ecdsa() { + verify_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + + fn verify_works_for_ecdsa_n_bls() { + verify_works::(); + } + + // Note that we use keys with and without a seed for this test. + fn public_keys_works< + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + >() + where + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, + ::Pair: BeefySignerAuthority, + { const TEST_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"test"); let store = keystore(); - let add_key = - |key_type, seed: Option<&str>| store.ecdsa_generate_new(key_type, seed).unwrap(); - // test keys - let _ = add_key(TEST_TYPE, Some(Keyring::Alice.to_seed().as_str())); - let _ = add_key(TEST_TYPE, Some(Keyring::Bob.to_seed().as_str())); - - let _ = add_key(TEST_TYPE, None); - let _ = add_key(TEST_TYPE, None); + let _ = generate_in_store::(store.clone(), TEST_TYPE, Some(Keyring::Alice)); + let _ = generate_in_store::(store.clone(), TEST_TYPE, Some(Keyring::Bob)); // BEEFY keys - let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Dave.to_seed().as_str())); - let _ = add_key(BEEFY_KEY_TYPE, Some(Keyring::Eve.to_seed().as_str())); + let _ = + generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Dave)); + let _ = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, Some(Keyring::Eve)); + + let _ = generate_in_store::(store.clone(), TEST_TYPE, None); + let _ = generate_in_store::(store.clone(), TEST_TYPE, None); - let key1: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into(); - let key2: ecdsa_crypto::Public = add_key(BEEFY_KEY_TYPE, None).into(); + let key1 = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, None); + let key2 = generate_in_store::(store.clone(), BEEFY_KEY_TYPE, None); - let store: BeefyKeystore = Some(store).into(); + let store: BeefyKeystore = Some(store).into(); let keys = store.public_keys().ok().unwrap(); @@ -340,4 +547,16 @@ pub mod tests { assert!(keys.contains(&key1)); assert!(keys.contains(&key2)); } + + #[test] + fn public_keys_works_for_ecdsa_keystore() { + public_keys_works::(); + } + + #[cfg(feature = "bls-experimental")] + #[test] + + fn public_keys_works_for_ecdsa_n_bls() { + public_keys_works::(); + } } diff --git a/substrate/client/consensus/beefy/src/lib.rs b/substrate/client/consensus/beefy/src/lib.rs index 1f10d8099d836b55fab2b13a60dcc2faf0f37320..323af1bc8305c38bf7a34b4690b212954b7b0864 100644 --- a/substrate/client/consensus/beefy/src/lib.rs +++ b/substrate/client/consensus/beefy/src/lib.rs @@ -31,7 +31,7 @@ use crate::{ import::BeefyBlockImport, metrics::register_metrics, }; -use futures::{stream::Fuse, StreamExt}; +use futures::{stream::Fuse, FutureExt, StreamExt}; use log::{debug, error, info, warn}; use parking_lot::Mutex; use prometheus::Registry; @@ -40,17 +40,21 @@ use sc_consensus::BlockImport; use sc_network::{NetworkRequest, NotificationService, ProtocolName}; use sc_network_gossip::{GossipEngine, Network as GossipNetwork, Syncing as GossipSyncing}; use sp_api::ProvideRuntimeApi; -use sp_blockchain::{ - Backend as BlockchainBackend, Error as ClientError, HeaderBackend, Result as ClientResult, -}; +use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend}; use sp_consensus::{Error as ConsensusError, SyncOracle}; use sp_consensus_beefy::{ - ecdsa_crypto::AuthorityId, BeefyApi, MmrRootHash, PayloadProvider, ValidatorSet, + ecdsa_crypto::AuthorityId, BeefyApi, ConsensusLog, MmrRootHash, PayloadProvider, ValidatorSet, + BEEFY_ENGINE_ID, }; use sp_keystore::KeystorePtr; use sp_mmr_primitives::MmrApi; use sp_runtime::traits::{Block, Header as HeaderT, NumberFor, Zero}; -use std::{collections::BTreeMap, marker::PhantomData, sync::Arc, time::Duration}; +use std::{ + collections::{BTreeMap, VecDeque}, + marker::PhantomData, + sync::Arc, + time::Duration, +}; mod aux_schema; mod error; @@ -63,9 +67,19 @@ pub mod communication; pub mod import; pub mod justification; +use crate::{ + communication::{gossip::GossipValidator, peers::PeerReport}, + justification::BeefyVersionedFinalityProof, + keystore::BeefyKeystore, + metrics::VoterMetrics, + round::Rounds, + worker::{BeefyWorker, PersistedState}, +}; pub use communication::beefy_protocol_name::{ gossip_protocol_name, justifications_protocol_name as justifs_protocol_name, }; +use sc_utils::mpsc::TracingUnboundedReceiver; +use sp_runtime::generic::OpaqueDigestItemId; #[cfg(test)] mod tests; @@ -209,6 +223,247 @@ pub struct BeefyParams { /// Handler for incoming BEEFY justifications requests from a remote peer. pub on_demand_justifications_handler: BeefyJustifsRequestHandler, } +/// Helper object holding BEEFY worker communication/gossip components. +/// +/// These are created once, but will be reused if worker is restarted/reinitialized. +pub(crate) struct BeefyComms { + pub gossip_engine: GossipEngine, + pub gossip_validator: Arc>, + pub gossip_report_stream: TracingUnboundedReceiver, + pub on_demand_justifications: OnDemandJustificationsEngine, +} + +/// Helper builder object for building [worker::BeefyWorker]. +/// +/// It has to do it in two steps: initialization and build, because the first step can sleep waiting +/// for certain chain and backend conditions, and while sleeping we still need to pump the +/// GossipEngine. Once initialization is done, the GossipEngine (and other pieces) are added to get +/// the complete [worker::BeefyWorker] object. +pub(crate) struct BeefyWorkerBuilder { + // utilities + backend: Arc, + runtime: Arc, + key_store: BeefyKeystore, + // voter metrics + metrics: Option, + persisted_state: PersistedState, +} + +impl BeefyWorkerBuilder +where + B: Block + codec::Codec, + BE: Backend, + R: ProvideRuntimeApi, + R::Api: BeefyApi, +{ + /// This will wait for the chain to enable BEEFY (if not yet enabled) and also wait for the + /// backend to sync all headers required by the voter to build a contiguous chain of mandatory + /// justifications. Then it builds the initial voter state using a combination of previously + /// persisted state in AUX DB and latest chain information/progress. + /// + /// Returns a sane `BeefyWorkerBuilder` that can build the `BeefyWorker`. + pub async fn async_initialize( + backend: Arc, + runtime: Arc, + key_store: BeefyKeystore, + metrics: Option, + min_block_delta: u32, + gossip_validator: Arc>, + finality_notifications: &mut Fuse>, + ) -> Result { + // Wait for BEEFY pallet to be active before starting voter. + let (beefy_genesis, best_grandpa) = + wait_for_runtime_pallet(&*runtime, finality_notifications).await?; + + let persisted_state = Self::load_or_init_state( + beefy_genesis, + best_grandpa, + min_block_delta, + backend.clone(), + runtime.clone(), + &key_store, + &metrics, + ) + .await?; + // Update the gossip validator with the right starting round and set id. + persisted_state + .gossip_filter_config() + .map(|f| gossip_validator.update_filter(f))?; + + Ok(BeefyWorkerBuilder { backend, runtime, key_store, metrics, persisted_state }) + } + + /// Takes rest of missing pieces as params and builds the `BeefyWorker`. + pub fn build( + self, + payload_provider: P, + sync: Arc, + comms: BeefyComms, + links: BeefyVoterLinks, + pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, + ) -> BeefyWorker { + BeefyWorker { + backend: self.backend, + runtime: self.runtime, + key_store: self.key_store, + metrics: self.metrics, + persisted_state: self.persisted_state, + payload_provider, + sync, + comms, + links, + pending_justifications, + } + } + + // 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. + async fn init_state( + beefy_genesis: NumberFor, + best_grandpa: ::Header, + min_block_delta: u32, + backend: Arc, + runtime: Arc, + ) -> Result, Error> { + let blockchain = backend.blockchain(); + + let beefy_genesis = runtime + .runtime_api() + .beefy_genesis(best_grandpa.hash()) + .ok() + .flatten() + .filter(|genesis| *genesis == beefy_genesis) + .ok_or_else(|| Error::Backend("BEEFY pallet expected to be active.".into()))?; + // Walk back the imported blocks and initialize voter either, at the last block with + // a BEEFY justification, or at pallet genesis block; voter will resume from there. + let 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()) + { + debug!( + target: LOG_TARGET, + "🥩 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.as_ref(), backend.as_ref(), &header).await?; + 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, + beefy_genesis, + ) + .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))?; + break state + } + + if *header.number() == beefy_genesis { + // We've reached BEEFY genesis, initialize voter here. + let genesis_set = + expect_validator_set(runtime.as_ref(), backend.as_ref(), &header).await?; + info!( + target: LOG_TARGET, + "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ + Starting voting rounds at block {:?}, genesis validator set {:?}.", + beefy_genesis, + genesis_set, + ); + + sessions.push_front(Rounds::new(beefy_genesis, genesis_set)); + break PersistedState::checked_new( + best_grandpa, + Zero::zero(), + sessions, + min_block_delta, + beefy_genesis, + ) + .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))? + } + + if let Some(active) = find_authorities_change::(&header) { + debug!( + target: LOG_TARGET, + "🥩 Marking block {:?} as BEEFY Mandatory.", + *header.number() + ); + sessions.push_front(Rounds::new(*header.number(), active)); + } + + // Move up the chain. + header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY).await?; + }; + + aux_schema::write_current_version(backend.as_ref())?; + aux_schema::write_voter_state(backend.as_ref(), &state)?; + Ok(state) + } + + async fn load_or_init_state( + beefy_genesis: NumberFor, + best_grandpa: ::Header, + min_block_delta: u32, + backend: Arc, + runtime: Arc, + key_store: &BeefyKeystore, + metrics: &Option, + ) -> Result, Error> { + // Initialize voter state from AUX DB if compatible. + if let Some(mut state) = crate::aux_schema::load_persistent(backend.as_ref())? + // Verify state pallet genesis matches runtime. + .filter(|state| state.pallet_genesis() == beefy_genesis) + { + // Overwrite persisted state with current best GRANDPA block. + state.set_best_grandpa(best_grandpa.clone()); + // Overwrite persisted data with newly provided `min_block_delta`. + state.set_min_block_delta(min_block_delta); + debug!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); + + // Make sure that all the headers that we need have been synced. + let mut new_sessions = vec![]; + let mut header = best_grandpa.clone(); + while *header.number() > state.best_beefy() { + if state.voting_oracle().can_add_session(*header.number()) { + if let Some(active) = find_authorities_change::(&header) { + new_sessions.push((active, *header.number())); + } + } + header = + wait_for_parent_header(backend.blockchain(), header, HEADER_SYNC_DELAY).await?; + } + + // Make sure we didn't miss any sessions during node restart. + for (validator_set, new_session_start) in new_sessions.drain(..).rev() { + debug!( + target: LOG_TARGET, + "🥩 Handling missed BEEFY session after node restart: {:?}.", + new_session_start + ); + state.init_session_at(new_session_start, validator_set, key_store, metrics); + } + return Ok(state) + } + + // No valid voter-state persisted, re-initialize from pallet genesis. + Self::init_state(beefy_genesis, best_grandpa, min_block_delta, backend, runtime).await + } +} /// Start the BEEFY gadget. /// @@ -277,7 +532,7 @@ pub async fn start_beefy_gadget( known_peers, prometheus_registry.clone(), ); - let mut beefy_comms = worker::BeefyComms { + let mut beefy_comms = BeefyComms { gossip_engine, gossip_validator, gossip_report_stream, @@ -287,57 +542,45 @@ pub async fn start_beefy_gadget( // We re-create and re-run the worker in this loop in order to quickly reinit and resume after // select recoverable errors. loop { - // Wait for BEEFY pallet to be active before starting voter. - let (beefy_genesis, best_grandpa) = match wait_for_runtime_pallet( - &*runtime, - &mut beefy_comms.gossip_engine, - &mut finality_notifications, - ) - .await - { - Ok(res) => res, - Err(e) => { - error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); - return - }, - }; - - let mut worker_base = worker::BeefyWorkerBase { - backend: backend.clone(), - runtime: runtime.clone(), - key_store: key_store.clone().into(), - metrics: metrics.clone(), - _phantom: Default::default(), - }; - - let persisted_state = match worker_base - .load_or_init_state(beefy_genesis, best_grandpa, min_block_delta) - .await - { - Ok(state) => state, - Err(e) => { - error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); - return - }, + // Make sure to pump gossip engine while waiting for initialization conditions. + let worker_builder = loop { + futures::select! { + builder_init_result = BeefyWorkerBuilder::async_initialize( + backend.clone(), + runtime.clone(), + key_store.clone().into(), + metrics.clone(), + min_block_delta, + beefy_comms.gossip_validator.clone(), + &mut finality_notifications, + ).fuse() => { + match builder_init_result { + Ok(builder) => break builder, + Err(e) => { + error!(target: LOG_TARGET, "🥩 Error: {:?}. Terminating.", e); + return + }, + } + }, + // Pump peer reports + _ = &mut beefy_comms.gossip_report_stream.next() => { + continue + }, + // Pump gossip engine. + _ = &mut beefy_comms.gossip_engine => { + error!(target: LOG_TARGET, "🥩 Gossip engine has unexpectedly terminated."); + return + } + } }; - // Update the gossip validator with the right starting round and set id. - if let Err(e) = persisted_state - .gossip_filter_config() - .map(|f| beefy_comms.gossip_validator.update_filter(f)) - { - error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); - return - } - let worker = worker::BeefyWorker { - base: worker_base, - payload_provider: payload_provider.clone(), - sync: sync.clone(), - comms: beefy_comms, - links: links.clone(), - pending_justifications: BTreeMap::new(), - persisted_state, - }; + let worker = worker_builder.build( + payload_provider.clone(), + sync.clone(), + beefy_comms, + links.clone(), + BTreeMap::new(), + ); match futures::future::select( Box::pin(worker.run(&mut block_import_justif, &mut finality_notifications)), @@ -404,9 +647,8 @@ where /// 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<(NumberFor, ::Header)> +) -> Result<(NumberFor, ::Header), Error> where B: Block, R: ProvideRuntimeApi, @@ -414,33 +656,24 @@ where { info!(target: LOG_TARGET, "🥩 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 = notif.header.hash(); - if let Some(start) = runtime.runtime_api().beefy_genesis(at).ok().flatten() { - if *notif.header.number() >= start { - // Beefy pallet available, return header for best grandpa at the time. - info!( - target: LOG_TARGET, - "🥩 BEEFY pallet available: block {:?} beefy genesis {:?}", - notif.header.number(), start - ); - return Ok((start, notif.header)) - } - } - }, - _ = gossip_engine => { - break + let notif = finality.next().await.ok_or_else(|| { + let err_msg = "🥩 Finality stream has unexpectedly terminated.".into(); + error!(target: LOG_TARGET, "{}", err_msg); + Error::Backend(err_msg) + })?; + let at = notif.header.hash(); + if let Some(start) = runtime.runtime_api().beefy_genesis(at).ok().flatten() { + if *notif.header.number() >= start { + // Beefy pallet available, return header for best grandpa at the time. + info!( + target: LOG_TARGET, + "🥩 BEEFY pallet available: block {:?} beefy genesis {:?}", + notif.header.number(), start + ); + return Ok((start, notif.header)) } } } - let err_msg = "🥩 Gossip engine has unexpectedly terminated.".into(); - error!(target: LOG_TARGET, "{}", err_msg); - Err(ClientError::Backend(err_msg)) } /// Provides validator set active `at_header`. It tries to get it from state, otherwise falls @@ -460,17 +693,21 @@ where R::Api: BeefyApi, { let blockchain = backend.blockchain(); - // Walk up the chain looking for the validator set active at 'at_header'. Process both state and // header digests. - debug!(target: LOG_TARGET, "🥩 Trying to find validator set active at header: {:?}", at_header); + debug!( + target: LOG_TARGET, + "🥩 Trying to find validator set active at header(number {:?}, hash {:?})", + at_header.number(), + at_header.hash() + ); let mut header = at_header.clone(); loop { debug!(target: LOG_TARGET, "🥩 Looking for auth set change at block number: {:?}", *header.number()); if let Ok(Some(active)) = runtime.runtime_api().validator_set(header.hash()) { return Ok(active) } else { - match worker::find_authorities_change::(&header) { + match find_authorities_change::(&header) { Some(active) => return Ok(active), // Move up the chain. Ultimately we'll get it from chain genesis state, or error out // there. @@ -482,3 +719,18 @@ where } } } + +/// 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. +pub(crate) fn find_authorities_change(header: &B::Header) -> Option> +where + B: Block, +{ + let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); + + let filter = |log: ConsensusLog| match log { + ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set), + _ => None, + }; + header.digest().convert_first(|l| l.try_to(id).and_then(filter)) +} diff --git a/substrate/client/consensus/beefy/src/round.rs b/substrate/client/consensus/beefy/src/round.rs index 47414c60fdb5fc8fc94e60136481ebaefd4d7560..0045dc70c260ee43ca1137d7d7f92b097220aeaf 100644 --- a/substrate/client/consensus/beefy/src/round.rs +++ b/substrate/client/consensus/beefy/src/round.rs @@ -207,7 +207,7 @@ mod tests { use sc_network_test::Block; use sp_consensus_beefy::{ - known_payloads::MMR_ROOT_ID, Commitment, EquivocationProof, Keyring, Payload, + known_payloads::MMR_ROOT_ID, test_utils::Keyring, Commitment, EquivocationProof, Payload, SignedCommitment, ValidatorSet, VoteMessage, }; @@ -226,7 +226,7 @@ mod tests { #[test] fn round_tracker() { let mut rt = RoundTracker::default(); - let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")); + let bob_vote = (Keyring::Bob.public(), Keyring::::Bob.sign(b"I am committed")); let threshold = 2; // adding new vote allowed @@ -237,7 +237,8 @@ mod tests { // vote is not done assert!(!rt.is_done(threshold)); - let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); + let alice_vote = + (Keyring::Alice.public(), Keyring::::Alice.sign(b"I am committed")); // adding new vote (self vote this time) allowed assert!(rt.add_vote(alice_vote)); @@ -271,7 +272,11 @@ mod tests { assert_eq!(42, rounds.validator_set_id()); assert_eq!(1, rounds.session_start()); assert_eq!( - &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + &vec![ + Keyring::::Alice.public(), + Keyring::::Bob.public(), + Keyring::::Charlie.public() + ], rounds.validators() ); } @@ -301,7 +306,7 @@ mod tests { let mut vote = VoteMessage { id: Keyring::Alice.public(), commitment: commitment.clone(), - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; // add 1st good vote assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); @@ -310,26 +315,26 @@ mod tests { assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); vote.id = Keyring::Dave.public(); - vote.signature = Keyring::Dave.sign(b"I am committed"); + vote.signature = Keyring::::Dave.sign(b"I am committed"); // invalid vote (Dave is not a validator) assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Invalid); vote.id = Keyring::Bob.public(); - vote.signature = Keyring::Bob.sign(b"I am committed"); + vote.signature = Keyring::::Bob.sign(b"I am committed"); // add 2nd good vote assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); vote.id = Keyring::Charlie.public(); - vote.signature = Keyring::Charlie.sign(b"I am committed"); + vote.signature = Keyring::::Charlie.sign(b"I am committed"); // add 3rd good vote -> round concluded -> signatures present assert_eq!( rounds.add_vote(vote.clone()), VoteImportResult::RoundConcluded(SignedCommitment { commitment, signatures: vec![ - Some(Keyring::Alice.sign(b"I am committed")), - Some(Keyring::Bob.sign(b"I am committed")), - Some(Keyring::Charlie.sign(b"I am committed")), + Some(Keyring::::Alice.sign(b"I am committed")), + Some(Keyring::::Bob.sign(b"I am committed")), + Some(Keyring::::Charlie.sign(b"I am committed")), None, ] }) @@ -337,7 +342,7 @@ mod tests { rounds.conclude(block_number); vote.id = Keyring::Eve.public(); - vote.signature = Keyring::Eve.sign(b"I am committed"); + vote.signature = Keyring::::Eve.sign(b"I am committed"); // Eve is a validator, but round was concluded, adding vote disallowed assert_eq!(rounds.add_vote(vote), VoteImportResult::Stale); } @@ -364,7 +369,7 @@ mod tests { let mut vote = VoteMessage { id: Keyring::Alice.public(), commitment, - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; // add vote for previous session, should fail assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale); @@ -407,22 +412,22 @@ mod tests { let mut alice_vote = VoteMessage { id: Keyring::Alice.public(), commitment: commitment.clone(), - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; let mut bob_vote = VoteMessage { id: Keyring::Bob.public(), commitment: commitment.clone(), - signature: Keyring::Bob.sign(b"I am committed"), + signature: Keyring::::Bob.sign(b"I am committed"), }; let mut charlie_vote = VoteMessage { id: Keyring::Charlie.public(), commitment, - signature: Keyring::Charlie.sign(b"I am committed"), + signature: Keyring::::Charlie.sign(b"I am committed"), }; let expected_signatures = vec![ - Some(Keyring::Alice.sign(b"I am committed")), - Some(Keyring::Bob.sign(b"I am committed")), - Some(Keyring::Charlie.sign(b"I am committed")), + Some(Keyring::::Alice.sign(b"I am committed")), + Some(Keyring::::Bob.sign(b"I am committed")), + Some(Keyring::::Charlie.sign(b"I am committed")), ]; // round 1 - only 2 out of 3 vote @@ -484,7 +489,7 @@ mod tests { let alice_vote1 = VoteMessage { id: Keyring::Alice.public(), commitment: commitment1, - signature: Keyring::Alice.sign(b"I am committed"), + signature: Keyring::::Alice.sign(b"I am committed"), }; let mut alice_vote2 = alice_vote1.clone(); alice_vote2.commitment = commitment2; diff --git a/substrate/client/consensus/beefy/src/tests.rs b/substrate/client/consensus/beefy/src/tests.rs index 7e61e877c1ddac692c6ae5f7199cf5ff35752ff7..d106c9dcd88165f929085972483e090dbf78c559 100644 --- a/substrate/client/consensus/beefy/src/tests.rs +++ b/substrate/client/consensus/beefy/src/tests.rs @@ -32,8 +32,8 @@ use crate::{ gossip_protocol_name, justification::*, wait_for_runtime_pallet, - worker::{BeefyWorkerBase, PersistedState}, - BeefyRPCLinks, BeefyVoterLinks, KnownPeers, + worker::PersistedState, + BeefyRPCLinks, BeefyVoterLinks, BeefyWorkerBuilder, KnownPeers, }; use futures::{future, stream::FuturesUnordered, Future, FutureExt, StreamExt}; use parking_lot::Mutex; @@ -57,9 +57,10 @@ use sp_consensus_beefy::{ ecdsa_crypto::{AuthorityId, Signature}, known_payloads, mmr::{find_mmr_root_digest, MmrRootProvider}, - BeefyApi, Commitment, ConsensusLog, EquivocationProof, Keyring as BeefyKeyring, MmrRootHash, - OpaqueKeyOwnershipProof, Payload, SignedCommitment, ValidatorSet, ValidatorSetId, - VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, + test_utils::Keyring as BeefyKeyring, + BeefyApi, Commitment, ConsensusLog, EquivocationProof, MmrRootHash, OpaqueKeyOwnershipProof, + Payload, SignedCommitment, ValidatorSet, ValidatorSetId, VersionedFinalityProof, VoteMessage, + BEEFY_ENGINE_ID, }; use sp_core::H256; use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr}; @@ -349,11 +350,11 @@ fn add_auth_change_digest(builder: &mut impl BlockBuilderExt, new_auth_set: Beef .unwrap(); } -pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { - keys.iter().map(|&key| key.public().into()).collect() +pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { + keys.iter().map(|key| key.public().into()).collect() } -pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> KeystorePtr { +pub(crate) fn create_beefy_keystore(authority: &BeefyKeyring) -> KeystorePtr { let keystore = MemoryKeystore::new(); keystore .ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&authority.to_seed())) @@ -367,33 +368,25 @@ async fn voter_init_setup( api: &TestApi, ) -> Result, Error> { let backend = net.peer(0).client().as_backend(); - let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let (gossip_validator, _) = GossipValidator::new(known_peers); - let gossip_validator = Arc::new(gossip_validator); - let mut gossip_engine = sc_network_gossip::GossipEngine::new( - net.peer(0).network_service().clone(), - net.peer(0).sync_service().clone(), - net.peer(0).take_notification_service(&beefy_gossip_proto_name()).unwrap(), - "/beefy/whatever", - gossip_validator, - None, - ); - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(api, &mut gossip_engine, finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { + let (beefy_genesis, best_grandpa) = wait_for_runtime_pallet(api, finality).await.unwrap(); + let key_store = None.into(); + let metrics = None; + BeefyWorkerBuilder::load_or_init_state( + beefy_genesis, + best_grandpa, + 1, backend, - runtime: Arc::new(api.clone()), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await + Arc::new(api.clone()), + &key_store, + &metrics, + ) + .await } // Spawns beefy voters. Returns a future to spawn on the runtime. fn initialize_beefy( net: &mut BeefyTestNet, - peers: Vec<(usize, &BeefyKeyring, Arc)>, + peers: Vec<(usize, &BeefyKeyring, Arc)>, min_block_delta: u32, ) -> impl Future where @@ -413,7 +406,7 @@ where for (peer_id, key, api) in peers.into_iter() { let peer = &net.peers[peer_id]; - let keystore = create_beefy_keystore(*key); + let keystore = create_beefy_keystore(key); let (_, _, peer_data) = net.make_block_import(peer.client().clone()); let PeerData { beefy_rpc_links, beefy_voter_links, .. } = peer_data; @@ -471,7 +464,7 @@ async fn run_for(duration: Duration, net: &Arc>) { pub(crate) fn get_beefy_streams( net: &mut BeefyTestNet, // peer index and key - peers: impl Iterator, + peers: impl Iterator)>, ) -> (Vec>, Vec>>) { let mut best_block_streams = Vec::new(); @@ -569,7 +562,7 @@ async fn streams_empty_after_timeout( async fn finalize_block_and_wait_for_beefy( net: &Arc>, // peer index and key - peers: impl Iterator + Clone, + peers: impl Iterator)> + Clone, finalize_target: &H256, expected_beefy: &[u64], ) { @@ -1064,32 +1057,7 @@ async fn should_initialize_voter_at_custom_genesis() { net.peer(0).client().as_client().finalize_block(hashes[8], None).unwrap(); // load persistent state - nothing in DB, should init at genesis - // - // NOTE: code from `voter_init_setup()` is moved here because the new network event system - // doesn't allow creating a new `GossipEngine` as the notification handle is consumed by the - // first `GossipEngine` - let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let (gossip_validator, _) = GossipValidator::new(known_peers); - let gossip_validator = Arc::new(gossip_validator); - let mut gossip_engine = sc_network_gossip::GossipEngine::new( - net.peer(0).network_service().clone(), - net.peer(0).sync_service().clone(), - net.peer(0).take_notification_service(&beefy_gossip_proto_name()).unwrap(), - "/beefy/whatever", - gossip_validator, - None, - ); - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { - backend: backend.clone(), - runtime: Arc::new(api), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - let persisted_state = - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // Test initialization at session boundary. // verify voter initialized with single session starting at block `custom_pallet_genesis` (7) @@ -1119,18 +1087,7 @@ async fn should_initialize_voter_at_custom_genesis() { net.peer(0).client().as_client().finalize_block(hashes[10], None).unwrap(); // load persistent state - state preset in DB, but with different pallet genesis - // the network state persists and uses the old `GossipEngine` initialized for `peer(0)` - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { - backend: backend.clone(), - runtime: Arc::new(api), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - let new_persisted_state = - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap(); + let new_persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // verify voter initialized with single session starting at block `new_pallet_genesis` (10) let sessions = new_persisted_state.voting_oracle().sessions(); @@ -1321,32 +1278,7 @@ async fn should_catch_up_when_loading_saved_voter_state() { let api = TestApi::with_validator_set(&validator_set); // load persistent state - nothing in DB, should init at genesis - // - // NOTE: code from `voter_init_setup()` is moved here because the new network event system - // doesn't allow creating a new `GossipEngine` as the notification handle is consumed by the - // first `GossipEngine` - let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let (gossip_validator, _) = GossipValidator::new(known_peers); - let gossip_validator = Arc::new(gossip_validator); - let mut gossip_engine = sc_network_gossip::GossipEngine::new( - net.peer(0).network_service().clone(), - net.peer(0).sync_service().clone(), - net.peer(0).take_notification_service(&beefy_gossip_proto_name()).unwrap(), - "/beefy/whatever", - gossip_validator, - None, - ); - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { - backend: backend.clone(), - runtime: Arc::new(api.clone()), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - let persisted_state = - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // Test initialization at session boundary. // verify voter initialized with two sessions starting at blocks 1 and 10 @@ -1373,18 +1305,7 @@ async fn should_catch_up_when_loading_saved_voter_state() { // finalize 25 without justifications net.peer(0).client().as_client().finalize_block(hashes[25], None).unwrap(); // load persistent state - state preset in DB - // the network state persists and uses the old `GossipEngine` initialized for `peer(0)` - let (beefy_genesis, best_grandpa) = - wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap(); - let mut worker_base = BeefyWorkerBase { - backend: backend.clone(), - runtime: Arc::new(api), - key_store: None.into(), - metrics: None, - _phantom: Default::default(), - }; - let persisted_state = - worker_base.load_or_init_state(beefy_genesis, best_grandpa, 1).await.unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // Verify voter initialized with old sessions plus a new one starting at block 20. // There shouldn't be any duplicates. diff --git a/substrate/client/consensus/beefy/src/worker.rs b/substrate/client/consensus/beefy/src/worker.rs index e67e3e0f76ad996c4666142bf28fead1aed200b6..c8eb19621ba5a6e45ea2be93e1e2e830492a9b81 100644 --- a/substrate/client/consensus/beefy/src/worker.rs +++ b/substrate/client/consensus/beefy/src/worker.rs @@ -17,46 +17,42 @@ // along with this program. If not, see . use crate::{ - aux_schema, communication::{ - gossip::{proofs_topic, votes_topic, GossipFilterCfg, GossipMessage, GossipValidator}, + gossip::{proofs_topic, votes_topic, GossipFilterCfg, GossipMessage}, peers::PeerReport, - request_response::outgoing_requests_engine::{OnDemandJustificationsEngine, ResponseInfo}, + request_response::outgoing_requests_engine::ResponseInfo, }, error::Error, - expect_validator_set, + find_authorities_change, justification::BeefyVersionedFinalityProof, - keystore::{BeefyKeystore, BeefySignatureHasher}, + keystore::BeefyKeystore, metric_inc, metric_set, metrics::VoterMetrics, round::{Rounds, VoteImportResult}, - wait_for_parent_header, BeefyVoterLinks, HEADER_SYNC_DELAY, LOG_TARGET, + BeefyComms, BeefyVoterLinks, LOG_TARGET, }; use codec::{Codec, Decode, DecodeAll, 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_gossip::GossipEngine; -use sc_utils::{mpsc::TracingUnboundedReceiver, notification::NotificationReceiver}; +use sc_utils::notification::NotificationReceiver; use sp_api::ProvideRuntimeApi; use sp_arithmetic::traits::{AtLeast32Bit, Saturating}; -use sp_blockchain::Backend as BlockchainBackend; use sp_consensus::SyncOracle; use sp_consensus_beefy::{ check_equivocation_proof, ecdsa_crypto::{AuthorityId, Signature}, - BeefyApi, Commitment, ConsensusLog, EquivocationProof, PayloadProvider, ValidatorSet, + BeefyApi, BeefySignatureHasher, Commitment, EquivocationProof, PayloadProvider, ValidatorSet, VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, }; use sp_runtime::{ - generic::{BlockId, OpaqueDigestItemId}, + generic::BlockId, traits::{Block, Header, NumberFor, Zero}, SaturatedConversion, }; use std::{ collections::{BTreeMap, BTreeSet, VecDeque}, fmt::Debug, - marker::PhantomData, sync::Arc, }; @@ -180,8 +176,8 @@ impl VoterOracle { } } - // Check if an observed session can be added to the Oracle. - fn can_add_session(&self, session_start: NumberFor) -> bool { + /// Check if an observed session can be added to the Oracle. + pub fn can_add_session(&self, session_start: NumberFor) -> bool { let latest_known_session_start = self.sessions.back().map(|session| session.session_start()); Some(session_start) > latest_known_session_start @@ -319,229 +315,28 @@ impl PersistedState { self.voting_oracle.best_grandpa_block_header = best_grandpa; } + pub fn voting_oracle(&self) -> &VoterOracle { + &self.voting_oracle + } + pub(crate) fn gossip_filter_config(&self) -> Result, Error> { let (start, end) = self.voting_oracle.accepted_interval()?; let validator_set = self.voting_oracle.current_validator_set()?; Ok(GossipFilterCfg { start, end, validator_set }) } -} - -/// Helper object holding BEEFY worker communication/gossip components. -/// -/// These are created once, but will be reused if worker is restarted/reinitialized. -pub(crate) struct BeefyComms { - pub gossip_engine: GossipEngine, - pub gossip_validator: Arc>, - pub gossip_report_stream: TracingUnboundedReceiver, - pub on_demand_justifications: OnDemandJustificationsEngine, -} - -pub(crate) struct BeefyWorkerBase { - // utilities - pub backend: Arc, - pub runtime: Arc, - pub key_store: BeefyKeystore, - - /// BEEFY client metrics. - pub metrics: Option, - - pub _phantom: PhantomData, -} - -impl BeefyWorkerBase -where - B: Block + Codec, - BE: Backend, - R: ProvideRuntimeApi, - R::Api: BeefyApi, -{ - // 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. - async fn init_state( - &self, - beefy_genesis: NumberFor, - best_grandpa: ::Header, - min_block_delta: u32, - ) -> Result, Error> { - let blockchain = self.backend.blockchain(); - - let beefy_genesis = self - .runtime - .runtime_api() - .beefy_genesis(best_grandpa.hash()) - .ok() - .flatten() - .filter(|genesis| *genesis == beefy_genesis) - .ok_or_else(|| Error::Backend("BEEFY pallet expected to be active.".into()))?; - // Walk back the imported blocks and initialize voter either, at the last block with - // a BEEFY justification, or at pallet genesis block; voter will resume from there. - let 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: LOG_TARGET, - "🥩 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(self.runtime.as_ref(), self.backend.as_ref(), &header) - .await?; - 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, - beefy_genesis, - ) - .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))?; - break state - } - - if *header.number() == beefy_genesis { - // We've reached BEEFY genesis, initialize voter here. - let genesis_set = - expect_validator_set(self.runtime.as_ref(), self.backend.as_ref(), &header) - .await?; - info!( - target: LOG_TARGET, - "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ - Starting voting rounds at block {:?}, genesis validator set {:?}.", - beefy_genesis, - genesis_set, - ); - - sessions.push_front(Rounds::new(beefy_genesis, genesis_set)); - break PersistedState::checked_new( - best_grandpa, - Zero::zero(), - sessions, - min_block_delta, - beefy_genesis, - ) - .ok_or_else(|| Error::Backend("Invalid BEEFY chain".into()))? - } - - if let Some(active) = find_authorities_change::(&header) { - info!( - target: LOG_TARGET, - "🥩 Marking block {:?} as BEEFY Mandatory.", - *header.number() - ); - sessions.push_front(Rounds::new(*header.number(), active)); - } - - // Move up the chain. - header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY).await?; - }; - - aux_schema::write_current_version(self.backend.as_ref())?; - aux_schema::write_voter_state(self.backend.as_ref(), &state)?; - Ok(state) - } - - pub async fn load_or_init_state( - &mut self, - beefy_genesis: NumberFor, - best_grandpa: ::Header, - min_block_delta: u32, - ) -> Result, Error> { - // Initialize voter state from AUX DB if compatible. - if let Some(mut state) = crate::aux_schema::load_persistent(self.backend.as_ref())? - // Verify state pallet genesis matches runtime. - .filter(|state| state.pallet_genesis() == beefy_genesis) - { - // Overwrite persisted state with current best GRANDPA block. - state.set_best_grandpa(best_grandpa.clone()); - // Overwrite persisted data with newly provided `min_block_delta`. - state.set_min_block_delta(min_block_delta); - info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); - - // Make sure that all the headers that we need have been synced. - let mut new_sessions = vec![]; - let mut header = best_grandpa.clone(); - while *header.number() > state.best_beefy() { - if state.voting_oracle.can_add_session(*header.number()) { - if let Some(active) = find_authorities_change::(&header) { - new_sessions.push((active, *header.number())); - } - } - header = - wait_for_parent_header(self.backend.blockchain(), header, HEADER_SYNC_DELAY) - .await?; - } - - // Make sure we didn't miss any sessions during node restart. - for (validator_set, new_session_start) in new_sessions.drain(..).rev() { - info!( - target: LOG_TARGET, - "🥩 Handling missed BEEFY session after node restart: {:?}.", - new_session_start - ); - self.init_session_at(&mut state, validator_set, new_session_start); - } - return Ok(state) - } - - // No valid voter-state persisted, re-initialize from pallet genesis. - self.init_state(beefy_genesis, best_grandpa, min_block_delta).await - } - - /// 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 - /// is part of the validator set, that's because if there are no local keys - /// then we can't perform our job as a validator. - /// - /// Note that for a non-authority node there will be no keystore, and we will - /// return an error and don't check. The error can usually be ignored. - fn verify_validator_set( - &self, - block: &NumberFor, - active: &ValidatorSet, - ) -> Result<(), Error> { - let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); - - let public_keys = self.key_store.public_keys()?; - let store: BTreeSet<&AuthorityId> = public_keys.iter().collect(); - - if store.intersection(&active).count() == 0 { - let msg = "no authority public key found in store".to_string(); - debug!(target: LOG_TARGET, "🥩 for block {:?} {}", block, msg); - metric_inc!(self.metrics, beefy_no_authority_found_in_store); - Err(Error::Keystore(msg)) - } else { - Ok(()) - } - } /// Handle session changes by starting new voting round for mandatory blocks. - fn init_session_at( + pub fn init_session_at( &mut self, - persisted_state: &mut PersistedState, - validator_set: ValidatorSet, new_session_start: NumberFor, + validator_set: ValidatorSet, + key_store: &BeefyKeystore, + metrics: &Option, ) { debug!(target: LOG_TARGET, "🥩 New active validator set: {:?}", validator_set); // BEEFY should finalize a mandatory block during each session. - if let Ok(active_session) = persisted_state.voting_oracle.active_rounds() { + if let Ok(active_session) = self.voting_oracle.active_rounds() { if !active_session.mandatory_done() { debug!( target: LOG_TARGET, @@ -549,20 +344,20 @@ where validator_set.id(), active_session.validator_set_id(), ); - metric_inc!(self.metrics, beefy_lagging_sessions); + metric_inc!(metrics, beefy_lagging_sessions); } } if log_enabled!(target: LOG_TARGET, log::Level::Debug) { // verify the new validator set - only do it if we're also logging the warning - let _ = self.verify_validator_set(&new_session_start, &validator_set); + if verify_validator_set::(&new_session_start, &validator_set, key_store).is_err() { + metric_inc!(metrics, beefy_no_authority_found_in_store); + } } let id = validator_set.id(); - persisted_state - .voting_oracle - .add_session(Rounds::new(new_session_start, validator_set)); - metric_set!(self.metrics, beefy_validator_set_id, id); + self.voting_oracle.add_session(Rounds::new(new_session_start, validator_set)); + metric_set!(metrics, beefy_validator_set_id, id); info!( target: LOG_TARGET, "🥩 New Rounds for validator set id: {:?} with session_start {:?}", @@ -574,9 +369,10 @@ where /// A BEEFY worker/voter that follows the BEEFY protocol pub(crate) struct BeefyWorker { - pub base: BeefyWorkerBase, - - // utils + // utilities + pub backend: Arc, + pub runtime: Arc, + pub key_store: BeefyKeystore, pub payload_provider: P, pub sync: Arc, @@ -592,6 +388,8 @@ pub(crate) struct BeefyWorker { pub pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, /// Persisted voter state. pub persisted_state: PersistedState, + /// BEEFY voter metrics + pub metrics: Option, } impl BeefyWorker @@ -622,24 +420,28 @@ where validator_set: ValidatorSet, new_session_start: NumberFor, ) { - self.base - .init_session_at(&mut self.persisted_state, validator_set, new_session_start); + self.persisted_state.init_session_at( + new_session_start, + validator_set, + &self.key_store, + &self.metrics, + ); } fn handle_finality_notification( &mut self, notification: &FinalityNotification, ) -> Result<(), Error> { + let header = ¬ification.header; debug!( target: LOG_TARGET, - "🥩 Finality notification: header {:?} tree_route {:?}", - notification.header, + "🥩 Finality notification: header(number {:?}, hash {:?}) tree_route {:?}", + header.number(), + header.hash(), notification.tree_route, ); - let header = ¬ification.header; - self.base - .runtime + self.runtime .runtime_api() .beefy_genesis(header.hash()) .ok() @@ -653,7 +455,7 @@ where self.persisted_state.set_best_grandpa(header.clone()); // Check all (newly) finalized blocks for new session(s). - let backend = self.base.backend.clone(); + let backend = self.backend.clone(); for header in notification .tree_route .iter() @@ -672,7 +474,7 @@ where } if new_session_added { - crate::aux_schema::write_voter_state(&*self.base.backend, &self.persisted_state) + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string()))?; } @@ -706,7 +508,7 @@ where true, ); }, - RoundAction::Drop => metric_inc!(self.base.metrics, beefy_stale_votes), + RoundAction::Drop => metric_inc!(self.metrics, beefy_stale_votes), RoundAction::Enqueue => error!(target: LOG_TARGET, "🥩 unexpected vote: {:?}.", vote), }; Ok(()) @@ -726,23 +528,23 @@ where match self.voting_oracle().triage_round(block_num)? { RoundAction::Process => { debug!(target: LOG_TARGET, "🥩 Process justification for round: {:?}.", block_num); - metric_inc!(self.base.metrics, beefy_imported_justifications); + metric_inc!(self.metrics, beefy_imported_justifications); self.finalize(justification)? }, RoundAction::Enqueue => { debug!(target: LOG_TARGET, "🥩 Buffer justification for round: {:?}.", block_num); if self.pending_justifications.len() < MAX_BUFFERED_JUSTIFICATIONS { self.pending_justifications.entry(block_num).or_insert(justification); - metric_inc!(self.base.metrics, beefy_buffered_justifications); + metric_inc!(self.metrics, beefy_buffered_justifications); } else { - metric_inc!(self.base.metrics, beefy_buffered_justifications_dropped); + metric_inc!(self.metrics, beefy_buffered_justifications_dropped); warn!( target: LOG_TARGET, "🥩 Buffer justification dropped for round: {:?}.", block_num ); } }, - RoundAction::Drop => metric_inc!(self.base.metrics, beefy_stale_justifications), + RoundAction::Drop => metric_inc!(self.metrics, beefy_stale_justifications), }; Ok(()) } @@ -757,14 +559,14 @@ where match rounds.add_vote(vote) { VoteImportResult::RoundConcluded(signed_commitment) => { let finality_proof = VersionedFinalityProof::V1(signed_commitment); - info!( + debug!( target: LOG_TARGET, "🥩 Round #{} concluded, finality_proof: {:?}.", block_number, finality_proof ); // We created the `finality_proof` and know to be valid. // New state is persisted after finalization. self.finalize(finality_proof.clone())?; - metric_inc!(self.base.metrics, beefy_good_votes_processed); + metric_inc!(self.metrics, beefy_good_votes_processed); return Ok(Some(finality_proof)) }, VoteImportResult::Ok => { @@ -775,20 +577,17 @@ where .map(|(mandatory_num, _)| mandatory_num == block_number) .unwrap_or(false) { - crate::aux_schema::write_voter_state( - &*self.base.backend, - &self.persisted_state, - ) - .map_err(|e| Error::Backend(e.to_string()))?; + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) + .map_err(|e| Error::Backend(e.to_string()))?; } - metric_inc!(self.base.metrics, beefy_good_votes_processed); + metric_inc!(self.metrics, beefy_good_votes_processed); }, VoteImportResult::Equivocation(proof) => { - metric_inc!(self.base.metrics, beefy_equivocation_votes); + metric_inc!(self.metrics, beefy_equivocation_votes); self.report_equivocation(proof)?; }, - VoteImportResult::Invalid => metric_inc!(self.base.metrics, beefy_invalid_votes), - VoteImportResult::Stale => metric_inc!(self.base.metrics, beefy_stale_votes), + VoteImportResult::Invalid => metric_inc!(self.metrics, beefy_invalid_votes), + VoteImportResult::Stale => metric_inc!(self.metrics, beefy_stale_votes), }; Ok(None) } @@ -815,15 +614,14 @@ where // Set new best BEEFY block number. self.persisted_state.set_best_beefy(block_num); - crate::aux_schema::write_voter_state(&*self.base.backend, &self.persisted_state) + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string()))?; - metric_set!(self.base.metrics, beefy_best_block, block_num); + metric_set!(self.metrics, beefy_best_block, block_num); self.comms.on_demand_justifications.cancel_requests_older_than(block_num); if let Err(e) = self - .base .backend .blockchain() .expect_block_hash_from_id(&BlockId::Number(block_num)) @@ -833,8 +631,7 @@ where .notify(|| Ok::<_, ()>(hash)) .expect("forwards closure result; the closure always returns Ok; qed."); - self.base - .backend + self.backend .append_justification(hash, (BEEFY_ENGINE_ID, finality_proof.encode())) }) { debug!( @@ -871,13 +668,13 @@ where for (num, justification) in justifs_to_process.into_iter() { debug!(target: LOG_TARGET, "🥩 Handle buffered justification for: {:?}.", num); - metric_inc!(self.base.metrics, beefy_imported_justifications); + metric_inc!(self.metrics, beefy_imported_justifications); if let Err(err) = self.finalize(justification) { error!(target: LOG_TARGET, "🥩 Error finalizing block: {}", err); } } metric_set!( - self.base.metrics, + self.metrics, beefy_buffered_justifications, self.pending_justifications.len() ); @@ -889,7 +686,7 @@ 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() { - metric_set!(self.base.metrics, beefy_should_vote_on, target); + metric_set!(self.metrics, beefy_should_vote_on, target); if target > self.persisted_state.best_voted { self.do_vote(target)?; } @@ -909,7 +706,6 @@ where self.persisted_state.voting_oracle.best_grandpa_block_header.clone() } else { let hash = self - .base .backend .blockchain() .expect_block_hash_from_id(&BlockId::Number(target_number)) @@ -921,7 +717,7 @@ where Error::Backend(err_msg) })?; - self.base.backend.blockchain().expect_header(hash).map_err(|err| { + self.backend.blockchain().expect_header(hash).map_err(|err| { let err_msg = format!( "Couldn't get header for block #{:?} ({:?}) (error: {:?}), skipping vote..", target_number, hash, err @@ -941,7 +737,7 @@ where let rounds = self.persisted_state.voting_oracle.active_rounds_mut()?; let (validators, validator_set_id) = (rounds.validators(), rounds.validator_set_id()); - let authority_id = if let Some(id) = self.base.key_store.authority_id(validators) { + let authority_id = if let Some(id) = self.key_store.authority_id(validators) { debug!(target: LOG_TARGET, "🥩 Local authority id: {:?}", id); id } else { @@ -955,7 +751,7 @@ where let commitment = Commitment { payload, block_number: target_number, validator_set_id }; let encoded_commitment = commitment.encode(); - let signature = match self.base.key_store.sign(&authority_id, &encoded_commitment) { + let signature = match self.key_store.sign(&authority_id, &encoded_commitment) { Ok(sig) => sig, Err(err) => { warn!(target: LOG_TARGET, "🥩 Error signing commitment: {:?}", err); @@ -980,7 +776,7 @@ where .gossip_engine .gossip_message(proofs_topic::(), encoded_proof, true); } else { - metric_inc!(self.base.metrics, beefy_votes_sent); + metric_inc!(self.metrics, beefy_votes_sent); debug!(target: LOG_TARGET, "🥩 Sent vote message: {:?}", vote); let encoded_vote = GossipMessage::::Vote(vote).encode(); self.comms.gossip_engine.gossip_message(votes_topic::(), encoded_vote, false); @@ -988,8 +784,8 @@ where // Persist state after vote to avoid double voting in case of voter restarts. self.persisted_state.best_voted = target_number; - metric_set!(self.base.metrics, beefy_best_voted, target_number); - crate::aux_schema::write_voter_state(&*self.base.backend, &self.persisted_state) + metric_set!(self.metrics, beefy_best_voted, target_number); + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string())) } @@ -1163,16 +959,15 @@ where if !check_equivocation_proof::<_, _, BeefySignatureHasher>(&proof) { debug!(target: LOG_TARGET, "🥩 Skip report for bad equivocation {:?}", proof); return Ok(()) - } else if let Some(local_id) = self.base.key_store.authority_id(validators) { + } else if let Some(local_id) = self.key_store.authority_id(validators) { if offender_id == local_id { - debug!(target: LOG_TARGET, "🥩 Skip equivocation report for own equivocation"); + warn!(target: LOG_TARGET, "🥩 Skip equivocation report for own equivocation"); return Ok(()) } } let number = *proof.round_number(); let hash = self - .base .backend .blockchain() .expect_block_hash_from_id(&BlockId::Number(number)) @@ -1183,7 +978,7 @@ where ); Error::Backend(err_msg) })?; - let runtime_api = self.base.runtime.runtime_api(); + let runtime_api = self.runtime.runtime_api(); // generate key ownership proof at that block let key_owner_proof = match runtime_api .generate_key_ownership_proof(hash, validator_set_id, offender_id) @@ -1200,7 +995,7 @@ where }; // submit equivocation report at **best** block - let best_block_hash = self.base.backend.blockchain().info().best_hash; + let best_block_hash = self.backend.blockchain().info().best_hash; runtime_api .submit_report_equivocation_unsigned_extrinsic(best_block_hash, proof, key_owner_proof) .map_err(Error::RuntimeApi)?; @@ -1209,21 +1004,6 @@ where } } -/// 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. -pub(crate) fn find_authorities_change(header: &B::Header) -> Option> -where - B: Block, -{ - let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); - - let filter = |log: ConsensusLog| match log { - ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set), - _ => None, - }; - header.digest().convert_first(|l| l.try_to(id).and_then(filter)) -} - /// Calculate next block number to vote on. /// /// Return `None` if there is no votable target yet. @@ -1234,7 +1014,7 @@ where // if the mandatory block (session_start) does not have a beefy justification yet, // we vote on it let target = if best_beefy < session_start { - debug!(target: LOG_TARGET, "🥩 vote target - mandatory block: #{:?}", session_start,); + debug!(target: LOG_TARGET, "🥩 vote target - mandatory block: #{:?}", session_start); session_start } else { let diff = best_grandpa.saturating_sub(best_beefy) + 1u32.into(); @@ -1260,11 +1040,42 @@ where } } +/// 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 +/// is part of the validator set, that's because if there are no local keys +/// then we can't perform our job as a validator. +/// +/// Note that for a non-authority node there will be no keystore, and we will +/// return an error and don't check. The error can usually be ignored. +fn verify_validator_set( + block: &NumberFor, + active: &ValidatorSet, + key_store: &BeefyKeystore, +) -> Result<(), Error> { + let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); + + let public_keys = key_store.public_keys()?; + let store: BTreeSet<&AuthorityId> = public_keys.iter().collect(); + + if store.intersection(&active).count() == 0 { + let msg = "no authority public key found in store".to_string(); + debug!(target: LOG_TARGET, "🥩 for block {:?} {}", block, msg); + Err(Error::Keystore(msg)) + } else { + Ok(()) + } +} + #[cfg(test)] pub(crate) mod tests { use super::*; use crate::{ - communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, + communication::{ + gossip::GossipValidator, + notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, + request_response::outgoing_requests_engine::OnDemandJustificationsEngine, + }, tests::{ create_beefy_keystore, get_beefy_streams, make_beefy_ids, BeefyPeer, BeefyTestNet, TestApi, @@ -1274,12 +1085,16 @@ pub(crate) mod tests { use futures::{future::poll_fn, task::Poll}; use parking_lot::Mutex; use sc_client_api::{Backend as BackendT, HeaderBackend}; + use sc_network_gossip::GossipEngine; use sc_network_sync::SyncingService; use sc_network_test::TestNetFactory; use sp_blockchain::Backend as BlockchainBackendT; use sp_consensus_beefy::{ - generate_equivocation_proof, known_payloads, known_payloads::MMR_ROOT_ID, - mmr::MmrRootProvider, Keyring, Payload, SignedCommitment, + known_payloads, + known_payloads::MMR_ROOT_ID, + mmr::MmrRootProvider, + test_utils::{generate_equivocation_proof, Keyring}, + ConsensusLog, Payload, SignedCommitment, }; use sp_runtime::traits::{Header as HeaderT, One}; use substrate_test_runtime_client::{ @@ -1288,10 +1103,6 @@ pub(crate) mod tests { }; impl PersistedState { - pub fn voting_oracle(&self) -> &VoterOracle { - &self.voting_oracle - } - pub fn active_round(&self) -> Result<&Rounds, Error> { self.voting_oracle.active_rounds() } @@ -1309,7 +1120,7 @@ pub(crate) mod tests { fn create_beefy_worker( peer: &mut BeefyPeer, - key: &Keyring, + key: &Keyring, min_block_delta: u32, genesis_validator_set: ValidatorSet, ) -> BeefyWorker< @@ -1319,7 +1130,7 @@ pub(crate) mod tests { TestApi, Arc>, > { - let keystore = create_beefy_keystore(*key); + let keystore = create_beefy_keystore(key); let (to_rpc_justif_sender, from_voter_justif_stream) = BeefyVersionedFinalityProofStream::::channel(); @@ -1387,13 +1198,10 @@ pub(crate) mod tests { on_demand_justifications, }; BeefyWorker { - base: BeefyWorkerBase { - backend, - runtime: api, - key_store: Some(keystore).into(), - metrics, - _phantom: Default::default(), - }, + backend, + runtime: api, + key_store: Some(keystore).into(), + metrics, payload_provider, sync: Arc::new(sync), links, @@ -1671,19 +1479,22 @@ pub(crate) mod tests { 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.base.verify_validator_set(&1, &validator_set), Ok(())); + assert_eq!(verify_validator_set::(&1, &validator_set, &worker.key_store), Ok(())); // unknown `Bob` key let keys = &[Keyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let err_msg = "no authority public key found in store".to_string(); let expected = Err(Error::Keystore(err_msg)); - assert_eq!(worker.base.verify_validator_set(&1, &validator_set), expected); + assert_eq!(verify_validator_set::(&1, &validator_set, &worker.key_store), expected); // worker has no keystore - worker.base.key_store = None.into(); + worker.key_store = None.into(); let expected_err = Err(Error::Keystore("no Keystore".into())); - assert_eq!(worker.base.verify_validator_set(&1, &validator_set), expected_err); + assert_eq!( + verify_validator_set::(&1, &validator_set, &worker.key_store), + expected_err + ); } #[tokio::test] @@ -1835,7 +1646,7 @@ pub(crate) mod tests { let mut net = BeefyTestNet::new(1); let mut worker = create_beefy_worker(net.peer(0), &keys[0], 1, validator_set.clone()); - worker.base.runtime = api_alice.clone(); + worker.runtime = api_alice.clone(); // let there be a block with num = 1: let _ = net.peer(0).push_blocks(1, false); diff --git a/substrate/client/consensus/common/Cargo.toml b/substrate/client/consensus/common/Cargo.toml index 16d3a4a1441ffcf0986ea9ee0418a770ded4a60d..7f36dfc09ef8451927eb32ebd651258647abee3b 100644 --- a/substrate/client/consensus/common/Cargo.toml +++ b/substrate/client/consensus/common/Cargo.toml @@ -20,11 +20,11 @@ async-trait = "0.1.74" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" libp2p-identity = { version = "0.1.3", features = ["ed25519", "peerid"] } -log = "0.4.17" +log = { workspace = true, default-features = true } mockall = "0.11.3" parking_lot = "0.12.1" -serde = { version = "1.0", features = ["derive"] } -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } sc-utils = { path = "../../utils" } diff --git a/substrate/client/consensus/grandpa/Cargo.toml b/substrate/client/consensus/grandpa/Cargo.toml index 3f7b48d9f2d0d2202170818d41d4730dcfb0bfa8..1ab953525220b82a500e5a23be0b94a673be51a2 100644 --- a/substrate/client/consensus/grandpa/Cargo.toml +++ b/substrate/client/consensus/grandpa/Cargo.toml @@ -24,12 +24,12 @@ dyn-clone = "1.0" finality-grandpa = { version = "0.16.2", features = ["derive-codec"] } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } parking_lot = "0.12.1" rand = "0.8.5" -serde_json = "1.0.111" -thiserror = "1.0" +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } fork-tree = { path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-block-builder = { path = "../../block-builder" } @@ -57,7 +57,7 @@ sp-runtime = { path = "../../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3.0" finality-grandpa = { version = "0.16.2", features = ["derive-codec", "test-helpers"] } -serde = "1.0.195" +serde = { workspace = true, default-features = true } tokio = "1.22.0" sc-network = { path = "../../network" } sc-network-test = { path = "../../network/test" } diff --git a/substrate/client/consensus/grandpa/rpc/Cargo.toml b/substrate/client/consensus/grandpa/rpc/Cargo.toml index ae9ab5d9d0794872c28f60f9694ee719c103d74e..f7e87415448e8c44952b957cf365fb36b089e4b0 100644 --- a/substrate/client/consensus/grandpa/rpc/Cargo.toml +++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml @@ -15,11 +15,11 @@ workspace = true [dependencies] finality-grandpa = { version = "0.16.2", features = ["derive-codec"] } futures = "0.3.16" -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } -log = "0.4.8" +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } +log = { workspace = true, default-features = true } parity-scale-codec = { version = "3.6.1", features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"] } -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sc-client-api = { path = "../../../api" } sc-consensus-grandpa = { path = ".." } sc-rpc = { path = "../../../rpc" } diff --git a/substrate/client/consensus/grandpa/rpc/src/lib.rs b/substrate/client/consensus/grandpa/rpc/src/lib.rs index 878cefacc479a1a221f3631db574c9191e520824..0557eab93e2956b56956f50edea118c9c30cbd76 100644 --- a/substrate/client/consensus/grandpa/rpc/src/lib.rs +++ b/substrate/client/consensus/grandpa/rpc/src/lib.rs @@ -273,7 +273,7 @@ mod tests { let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); - assert_eq!(expected_response, response.result); + assert_eq!(expected_response, response); } #[tokio::test] @@ -295,7 +295,7 @@ mod tests { let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap(); - assert_eq!(expected_response, response.result); + assert_eq!(expected_response, response); } #[tokio::test] @@ -317,7 +317,7 @@ mod tests { .unwrap(); let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - assert_eq!(response.result, expected); + assert_eq!(response, expected); } fn create_justification() -> GrandpaJustification { diff --git a/substrate/client/consensus/manual-seal/Cargo.toml b/substrate/client/consensus/manual-seal/Cargo.toml index 0094fb8780095b355e9fe621f90f9d090d1c5eec..ac32fed72289fd6a296de2720a75fb36c65ad1f8 100644 --- a/substrate/client/consensus/manual-seal/Cargo.toml +++ b/substrate/client/consensus/manual-seal/Cargo.toml @@ -16,15 +16,15 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } assert_matches = "1.3.0" async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" -serde = { version = "1.0", features = ["derive"] } -thiserror = "1.0" +log = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } sc-consensus = { path = "../common" } diff --git a/substrate/client/consensus/pow/Cargo.toml b/substrate/client/consensus/pow/Cargo.toml index c59a6a271143264fa1e03dadc95302a4bc0ad67f..0791514035b43c83492175bcac67e2a6eb6aa9c6 100644 --- a/substrate/client/consensus/pow/Cargo.toml +++ b/substrate/client/consensus/pow/Cargo.toml @@ -20,9 +20,9 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-client-api = { path = "../../api" } sc-consensus = { path = "../common" } diff --git a/substrate/client/consensus/slots/Cargo.toml b/substrate/client/consensus/slots/Cargo.toml index 8eed24532c9fd3649f6f8dd6dc9f9a4914579e2d..75f8b29a2fd755c18d68b817499e2dfb75ea232c 100644 --- a/substrate/client/consensus/slots/Cargo.toml +++ b/substrate/client/consensus/slots/Cargo.toml @@ -21,7 +21,7 @@ async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } sc-client-api = { path = "../../api" } sc-consensus = { path = "../common" } sc-telemetry = { path = "../../telemetry" } diff --git a/substrate/client/db/Cargo.toml b/substrate/client/db/Cargo.toml index ed7b4178f287a67c72946b9829b91d91c84bb442..57ee1a8ad3315036f44e5b560fb55fa3f31c97e5 100644 --- a/substrate/client/db/Cargo.toml +++ b/substrate/client/db/Cargo.toml @@ -24,7 +24,7 @@ kvdb = "0.13.0" kvdb-memorydb = "0.13.0" kvdb-rocksdb = { version = "0.19.0", optional = true } linked-hash-map = "0.5.4" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-db = "0.4.12" parking_lot = "0.12.1" sc-client-api = { path = "../api" } diff --git a/substrate/client/db/src/bench.rs b/substrate/client/db/src/bench.rs index 03ad4817b53bcea2cb349d4d05c190bb239530fa..32503cf63c0ad7eeae1ab502d38b09aa210d9bc9 100644 --- a/substrate/client/db/src/bench.rs +++ b/substrate/client/db/src/bench.rs @@ -19,7 +19,7 @@ //! State backend that's useful for benchmarking use crate::{DbState, DbStateBuilder}; -use hash_db::{Hasher, Prefix}; +use hash_db::{Hasher as DbHasher, Prefix}; use kvdb::{DBTransaction, KeyValueDB}; use linked_hash_map::LinkedHashMap; use parking_lot::Mutex; @@ -27,10 +27,7 @@ use sp_core::{ hexdisplay::HexDisplay, storage::{ChildInfo, TrackedStorageKey}, }; -use sp_runtime::{ - traits::{Block as BlockT, HashingFor}, - StateVersion, Storage, -}; +use sp_runtime::{traits::Hash, StateVersion, Storage}; use sp_state_machine::{ backend::Backend as StateBackend, BackendTransaction, ChildStorageCollection, DBValue, IterArgs, StorageCollection, StorageIterator, StorageKey, StorageValue, @@ -45,16 +42,16 @@ use std::{ sync::Arc, }; -type State = DbState; +type State = DbState; -struct StorageDb { +struct StorageDb { db: Arc, - _block: std::marker::PhantomData, + _phantom: std::marker::PhantomData, } -impl sp_state_machine::Storage> for StorageDb { - fn get(&self, key: &Block::Hash, prefix: Prefix) -> Result, String> { - let prefixed_key = prefixed_key::>(key, prefix); +impl sp_state_machine::Storage for StorageDb { + fn get(&self, key: &Hasher::Output, prefix: Prefix) -> Result, String> { + let prefixed_key = prefixed_key::(key, prefix); self.db .get(0, &prefixed_key) .map_err(|e| format!("Database backend error: {:?}", e)) @@ -75,29 +72,29 @@ struct KeyTracker { } /// State that manages the backend database reference. Allows runtime to control the database. -pub struct BenchmarkingState { - root: Cell, - genesis_root: B::Hash, - state: RefCell>>, +pub struct BenchmarkingState { + root: Cell, + genesis_root: Hasher::Output, + state: RefCell>>, db: Cell>>, genesis: HashMap, (Vec, i32)>, record: Cell>>, key_tracker: Arc>, whitelist: RefCell>, - proof_recorder: Option>>, - proof_recorder_root: Cell, - shared_trie_cache: SharedTrieCache>, + proof_recorder: Option>, + proof_recorder_root: Cell, + shared_trie_cache: SharedTrieCache, } /// A raw iterator over the `BenchmarkingState`. -pub struct RawIter { - inner: as StateBackend>>::RawIter, +pub struct RawIter { + inner: as StateBackend>::RawIter, child_trie: Option>, key_tracker: Arc>, } -impl StorageIterator> for RawIter { - type Backend = BenchmarkingState; +impl StorageIterator for RawIter { + type Backend = BenchmarkingState; type Error = String; fn next_key(&mut self, backend: &Self::Backend) -> Option> { @@ -128,7 +125,7 @@ impl StorageIterator> for RawIter { } } -impl BenchmarkingState { +impl BenchmarkingState { /// Create a new instance that creates a database in a temporary dir. pub fn new( genesis: Storage, @@ -137,9 +134,9 @@ impl BenchmarkingState { enable_tracking: bool, ) -> Result { let state_version = sp_runtime::StateVersion::default(); - let mut root = B::Hash::default(); - let mut mdb = MemoryDB::>::default(); - sp_trie::trie_types::TrieDBMutBuilderV1::>::new(&mut mdb, &mut root).build(); + let mut root = Default::default(); + let mut mdb = MemoryDB::::default(); + sp_trie::trie_types::TrieDBMutBuilderV1::::new(&mut mdb, &mut root).build(); let mut state = BenchmarkingState { state: RefCell::new(None), @@ -169,7 +166,7 @@ impl BenchmarkingState { child_content.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), ) }); - let (root, transaction): (B::Hash, _) = + let (root, transaction): (Hasher::Output, _) = state.state.borrow().as_ref().unwrap().full_storage_root( genesis.top.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), child_delta, @@ -193,9 +190,9 @@ impl BenchmarkingState { recorder.reset(); self.proof_recorder_root.set(self.root.get()); } - let storage_db = Arc::new(StorageDb:: { db, _block: Default::default() }); + let storage_db = Arc::new(StorageDb:: { db, _phantom: Default::default() }); *self.state.borrow_mut() = Some( - DbStateBuilder::::new(storage_db, self.root.get()) + DbStateBuilder::::new(storage_db, self.root.get()) .with_optional_recorder(self.proof_recorder.clone()) .with_cache(self.shared_trie_cache.local_cache()) .build(), @@ -341,17 +338,17 @@ fn state_err() -> String { "State is not open".into() } -impl StateBackend> for BenchmarkingState { - type Error = as StateBackend>>::Error; - type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; - type RawIter = RawIter; +impl StateBackend for BenchmarkingState { + type Error = as StateBackend>::Error; + type TrieBackendStorage = as StateBackend>::TrieBackendStorage; + type RawIter = RawIter; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.storage(key) } - fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { + fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.storage_hash(key) } @@ -373,7 +370,7 @@ impl StateBackend> for BenchmarkingState { &self, child_info: &ChildInfo, key: &[u8], - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { self.add_read_key(Some(child_info.storage_key()), key); self.state .borrow() @@ -385,7 +382,7 @@ impl StateBackend> for BenchmarkingState { fn closest_merkle_value( &self, key: &[u8], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { self.add_read_key(None, key); self.state.borrow().as_ref().ok_or_else(state_err)?.closest_merkle_value(key) } @@ -394,7 +391,7 @@ impl StateBackend> for BenchmarkingState { &self, child_info: &ChildInfo, key: &[u8], - ) -> Result>, Self::Error> { + ) -> Result>, Self::Error> { self.add_read_key(None, key); self.state .borrow() @@ -443,7 +440,7 @@ impl StateBackend> for BenchmarkingState { &self, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, BackendTransaction>) { + ) -> (Hasher::Output, BackendTransaction) { self.state .borrow() .as_ref() @@ -455,7 +452,7 @@ impl StateBackend> for BenchmarkingState { child_info: &ChildInfo, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, bool, BackendTransaction>) { + ) -> (Hasher::Output, bool, BackendTransaction) { self.state .borrow() .as_ref() @@ -479,8 +476,8 @@ impl StateBackend> for BenchmarkingState { fn commit( &self, - storage_root: as Hasher>::Out, - mut transaction: BackendTransaction>, + storage_root: ::Out, + mut transaction: BackendTransaction, main_storage_changes: StorageCollection, child_storage_changes: ChildStorageCollection, ) -> Result<(), Self::Error> { @@ -634,8 +631,7 @@ impl StateBackend> for BenchmarkingState { log::debug!(target: "benchmark", "Some proof size: {}", &proof_size); proof_size } else { - if let Some(size) = proof.encoded_compact_size::>(proof_recorder_root) - { + if let Some(size) = proof.encoded_compact_size::(proof_recorder_root) { size as u32 } else if proof_recorder_root == self.root.get() { log::debug!(target: "benchmark", "No changes - no proof"); @@ -654,7 +650,7 @@ impl StateBackend> for BenchmarkingState { } } -impl std::fmt::Debug for BenchmarkingState { +impl std::fmt::Debug for BenchmarkingState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Bench DB") } @@ -663,6 +659,7 @@ impl std::fmt::Debug for BenchmarkingState { #[cfg(test)] mod test { use crate::bench::BenchmarkingState; + use sp_runtime::traits::HashingFor; use sp_state_machine::backend::Backend as _; fn hex(hex: &str) -> Vec { @@ -681,7 +678,8 @@ mod test { ..sp_runtime::Storage::default() }; let bench_state = - BenchmarkingState::::new(storage, None, false, true).unwrap(); + BenchmarkingState::>::new(storage, None, false, true) + .unwrap(); assert_eq!(bench_state.read_write_count(), (0, 0, 0, 0)); assert_eq!(bench_state.keys(Default::default()).unwrap().count(), 1); @@ -690,9 +688,13 @@ mod test { #[test] fn read_to_main_and_child_tries() { - let bench_state = - BenchmarkingState::::new(Default::default(), None, false, true) - .unwrap(); + let bench_state = BenchmarkingState::>::new( + Default::default(), + None, + false, + true, + ) + .unwrap(); for _ in 0..2 { let child1 = sp_core::storage::ChildInfo::new_default(b"child1"); diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 2d8622d5f12dcc798cbc4c46685df6b4a2a38658..0faa90dfc4f925db8776c6914fcf864813b790a5 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -101,14 +101,11 @@ pub use bench::BenchmarkingState; const CACHE_HEADERS: usize = 8; /// DB-backed patricia trie state, transaction type is an overlay of changes to commit. -pub type DbState = - sp_state_machine::TrieBackend>>, HashingFor>; +pub type DbState = sp_state_machine::TrieBackend>, H>; /// Builder for [`DbState`]. -pub type DbStateBuilder = sp_state_machine::TrieBackendBuilder< - Arc>>, - HashingFor, ->; +pub type DbStateBuilder = + sp_state_machine::TrieBackendBuilder>, Hasher>; /// Length of a [`DbHash`]. const DB_HASH_LEN: usize = 32; @@ -135,13 +132,17 @@ enum DbExtrinsic { /// It makes sure that the hash we are using stays pinned in storage /// until this structure is dropped. pub struct RefTrackingState { - state: DbState, + state: DbState>, storage: Arc>, parent_hash: Option, } impl RefTrackingState { - fn new(state: DbState, storage: Arc>, parent_hash: Option) -> Self { + fn new( + state: DbState>, + storage: Arc>, + parent_hash: Option, + ) -> Self { RefTrackingState { state, parent_hash, storage } } } @@ -162,12 +163,12 @@ impl std::fmt::Debug for RefTrackingState { /// A raw iterator over the `RefTrackingState`. pub struct RawIter { - inner: as StateBackend>>::RawIter, + inner: > as StateBackend>>::RawIter, } impl StorageIterator> for RawIter { type Backend = RefTrackingState; - type Error = as StateBackend>>::Error; + type Error = > as StateBackend>>::Error; fn next_key(&mut self, backend: &Self::Backend) -> Option> { self.inner.next_key(&backend.state) @@ -186,8 +187,9 @@ impl StorageIterator> for RawIter { } impl StateBackend> for RefTrackingState { - type Error = as StateBackend>>::Error; - type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + type Error = > as StateBackend>>::Error; + type TrieBackendStorage = + > as StateBackend>>::TrieBackendStorage; type RawIter = RawIter; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { @@ -284,7 +286,8 @@ impl StateBackend> for RefTrackingState { } impl AsTrieBackend> for RefTrackingState { - type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + type TrieBackendStorage = + > as StateBackend>>::TrieBackendStorage; fn as_trie_backend( &self, @@ -1936,7 +1939,7 @@ impl Backend { fn empty_state(&self) -> RecordStatsState, Block> { let root = EmptyStorage::::new().0; // Empty trie - let db_state = DbStateBuilder::::new(self.storage.clone(), root) + let db_state = DbStateBuilder::>::new(self.storage.clone(), root) .with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache())) .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), None); @@ -2428,9 +2431,12 @@ impl sc_client_api::backend::Backend for Backend { 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) - .with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache())) - .build(); + let db_state = + DbStateBuilder::>::new(genesis_state.clone(), root) + .with_optional_cache( + self.shared_trie_cache.as_ref().map(|c| c.local_cache()), + ) + .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), None); return Ok(RecordStatsState::new(state, None, self.state_usage.clone())) @@ -2449,11 +2455,12 @@ impl sc_client_api::backend::Backend for Backend { 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) - .with_optional_cache( - self.shared_trie_cache.as_ref().map(|c| c.local_cache()), - ) - .build(); + let db_state = + DbStateBuilder::>::new(self.storage.clone(), root) + .with_optional_cache( + self.shared_trie_cache.as_ref().map(|c| c.local_cache()), + ) + .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), Some(hash)); Ok(RecordStatsState::new(state, Some(hash), self.state_usage.clone())) } else { @@ -2524,7 +2531,7 @@ impl sc_client_api::backend::Backend for Backend { self.storage.state_db.pin(&hash, number.saturated_into::(), hint).map_err( |_| { sp_blockchain::Error::UnknownBlock(format!( - "State already discarded for `{:?}`", + "Unable to pin: state already discarded for `{:?}`", hash )) }, diff --git a/substrate/client/db/src/pinned_blocks_cache.rs b/substrate/client/db/src/pinned_blocks_cache.rs index 46c9287fb19ac1361153dbb65f4f0b353170042f..ac4aad07765cfbaa4d9c66efa4b44730b0b0a04c 100644 --- a/substrate/client/db/src/pinned_blocks_cache.rs +++ b/substrate/client/db/src/pinned_blocks_cache.rs @@ -20,7 +20,7 @@ use schnellru::{Limiter, LruMap}; use sp_runtime::{traits::Block as BlockT, Justifications}; const LOG_TARGET: &str = "db::pin"; -const PINNING_CACHE_SIZE: usize = 1024; +const PINNING_CACHE_SIZE: usize = 2048; /// Entry for pinned blocks cache. struct PinnedBlockCacheEntry { diff --git a/substrate/client/executor/common/Cargo.toml b/substrate/client/executor/common/Cargo.toml index 648fb9f0f5040d60d96defb07bc276e6e10cdc23..bfd1fa6b74014bfbac893bd4e503e70bd76d9cf0 100644 --- a/substrate/client/executor/common/Cargo.toml +++ b/substrate/client/executor/common/Cargo.toml @@ -17,7 +17,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = "1.0.48" +thiserror = { workspace = true } wasm-instrument = "0.4" sc-allocator = { path = "../../allocator" } sp-maybe-compressed-blob = { path = "../../../primitives/maybe-compressed-blob" } diff --git a/substrate/client/executor/src/lib.rs b/substrate/client/executor/src/lib.rs index 25bad81938f383e66eb1e63fb1c8ddcbea3a387f..6b99f0a6ee03b303d2d97ce05040828e3248312b 100644 --- a/substrate/client/executor/src/lib.rs +++ b/substrate/client/executor/src/lib.rs @@ -29,7 +29,6 @@ //! wasm engine used, instance cache. #![warn(missing_docs)] -#![recursion_limit = "128"] #[macro_use] mod executor; diff --git a/substrate/client/executor/wasmtime/Cargo.toml b/substrate/client/executor/wasmtime/Cargo.toml index 12e6647c69522f24d9e51b3b36c87a759311c955..75cc76a235430fa33b735e8b234493a24a3e2961 100644 --- a/substrate/client/executor/wasmtime/Cargo.toml +++ b/substrate/client/executor/wasmtime/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } cfg-if = "1.0" libc = "0.2.152" parking_lot = "0.12.1" diff --git a/substrate/client/informant/Cargo.toml b/substrate/client/informant/Cargo.toml index 0252e9a11572cc5f99cb30b513820e4c4f922473..bd15e94ebafab26c89f76927d9d2f9c576ca1541 100644 --- a/substrate/client/informant/Cargo.toml +++ b/substrate/client/informant/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] ansi_term = "0.12.1" futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.17" +log = { workspace = true, default-features = true } sc-client-api = { path = "../api" } sc-network-common = { path = "../network/common" } sc-network-sync = { path = "../network/sync" } diff --git a/substrate/client/keystore/Cargo.toml b/substrate/client/keystore/Cargo.toml index dc6a0fe29a84876c5c117a199017d1e8bda81590..908e0aa8f38ced48facaf99d05c0d7771f03fa26 100644 --- a/substrate/client/keystore/Cargo.toml +++ b/substrate/client/keystore/Cargo.toml @@ -19,8 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" parking_lot = "0.12.1" -serde_json = "1.0.111" -thiserror = "1.0" +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } sp-application-crypto = { path = "../../primitives/application-crypto" } sp-core = { path = "../../primitives/core" } sp-keystore = { path = "../../primitives/keystore" } diff --git a/substrate/client/keystore/src/local.rs b/substrate/client/keystore/src/local.rs index 3b29f435e2a942ffe96dd02a8009af4d33693995..8b922c11cbca990a0f4f87e4e3f25bbec2a55290 100644 --- a/substrate/client/keystore/src/local.rs +++ b/substrate/client/keystore/src/local.rs @@ -37,7 +37,7 @@ use sp_core::bandersnatch; } sp_keystore::bls_experimental_enabled! { -use sp_core::{bls377, bls381, ecdsa_bls377}; +use sp_core::{bls377, bls381, ecdsa_bls377, KeccakHasher}; } use crate::{Error, Result}; @@ -47,6 +47,13 @@ pub struct LocalKeystore(RwLock); impl LocalKeystore { /// Create a local keystore from filesystem. + /// + /// The keystore will be created at `path`. The keystore optionally supports to encrypt/decrypt + /// the keys in the keystore using `password`. + /// + /// NOTE: Even when passing a `password`, the keys on disk appear to look like normal secret + /// uris. However, without having the correct password the secret uri will not generate the + /// correct private key. See [`SecretUri`](sp_core::crypto::SecretUri) for more information. pub fn open>(path: T, password: Option) -> Result { let inner = KeystoreInner::open(path, password)?; Ok(Self(RwLock::new(inner))) @@ -136,6 +143,13 @@ impl LocalKeystore { } impl Keystore for LocalKeystore { + /// Insert a new secret key. + /// + /// WARNING: if the secret keypair has been manually generated using a password + /// (e.g. using methods such as [`sp_core::crypto::Pair::from_phrase`]) then such + /// a password must match the one used to open the keystore via [`LocalKeystore::open`]. + /// If the passwords doesn't match then the inserted key ends up being unusable under + /// the current keystore instance. fn insert( &self, key_type: KeyTypeId, @@ -391,6 +405,20 @@ impl Keystore for LocalKeystore { self.sign::(key_type, public, msg) } + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + let sig = self.0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.sign_with_hasher::(msg)); + Ok(sig) + } + + } } diff --git a/substrate/client/merkle-mountain-range/Cargo.toml b/substrate/client/merkle-mountain-range/Cargo.toml index 201c179f302c729d4c4a5864dd44ed9bc0e2896e..60232bccb0e08d1fb65aeb6d94723c03fcf77fa1 100644 --- a/substrate/client/merkle-mountain-range/Cargo.toml +++ b/substrate/client/merkle-mountain-range/Cargo.toml @@ -16,7 +16,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3" -log = "0.4" +log = { workspace = true, default-features = true } sp-api = { path = "../../primitives/api" } sp-blockchain = { path = "../../primitives/blockchain" } sc-client-api = { path = "../api" } diff --git a/substrate/client/merkle-mountain-range/rpc/Cargo.toml b/substrate/client/merkle-mountain-range/rpc/Cargo.toml index 99467e5468f07c826bc18d05f91e193e27041298..9b391b76ea00b5e48700b7213afb691c149167ab 100644 --- a/substrate/client/merkle-mountain-range/rpc/Cargo.toml +++ b/substrate/client/merkle-mountain-range/rpc/Cargo.toml @@ -16,8 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } -serde = { version = "1.0.195", features = ["derive"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { path = "../../../primitives/api" } sp-blockchain = { path = "../../../primitives/blockchain" } sp-core = { path = "../../../primitives/core" } @@ -25,4 +25,4 @@ sp-mmr-primitives = { path = "../../../primitives/merkle-mountain-range" } sp-runtime = { path = "../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } diff --git a/substrate/client/mixnet/Cargo.toml b/substrate/client/mixnet/Cargo.toml index 280af81b86ef631b5c99588eef28f20471c65bc8..736184f4668c8c96c660502774da90aedd85b731 100644 --- a/substrate/client/mixnet/Cargo.toml +++ b/substrate/client/mixnet/Cargo.toml @@ -24,7 +24,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = futures = "0.3.25" futures-timer = "3.0.2" libp2p-identity = { version = "0.1.3", features = ["peerid"] } -log = "0.4.17" +log = { workspace = true, default-features = true } mixnet = "0.7.0" multiaddr = "0.17.1" parking_lot = "0.12.1" @@ -37,4 +37,4 @@ sp-core = { path = "../../primitives/core" } sp-keystore = { path = "../../primitives/keystore" } sp-mixnet = { path = "../../primitives/mixnet" } sp-runtime = { path = "../../primitives/runtime" } -thiserror = "1.0" +thiserror = { workspace = true } diff --git a/substrate/client/network-gossip/Cargo.toml b/substrate/client/network-gossip/Cargo.toml index baf4def0b8e7d2fbfca34c17d6680d37b65fdf71..a14761c0d6e81e4eeda2e1e3d8dacd5fd7b99d4a 100644 --- a/substrate/client/network-gossip/Cargo.toml +++ b/substrate/client/network-gossip/Cargo.toml @@ -21,7 +21,7 @@ ahash = "0.8.2" futures = "0.3.21" futures-timer = "3.0.1" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } schnellru = "0.2.1" tracing = "0.1.29" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } diff --git a/substrate/client/network/Cargo.toml b/substrate/client/network/Cargo.toml index 167f112705be32a34bbdf062e0499d4c286c40fd..cbf74440dc1a83933d1e994294a7d37609c72331 100644 --- a/substrate/client/network/Cargo.toml +++ b/substrate/client/network/Cargo.toml @@ -30,16 +30,16 @@ futures-timer = "3.0.2" ip_network = "0.4.1" libp2p = { version = "0.51.4", features = ["dns", "identify", "kad", "macros", "mdns", "noise", "ping", "request-response", "tcp", "tokio", "websocket", "yamux"] } linked_hash_set = "0.1.3" -log = "0.4.17" +log = { workspace = true, default-features = true } mockall = "0.11.3" parking_lot = "0.12.1" partial_sort = "0.2.0" pin-project = "1.0.12" rand = "0.8.5" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } smallvec = "1.11.0" -thiserror = "1.0" +thiserror = { workspace = true } tokio = { version = "1.22.0", features = ["macros", "sync"] } tokio-stream = "0.1.7" unsigned-varint = { version = "0.7.1", features = ["asynchronous_codec", "futures"] } diff --git a/substrate/client/network/bitswap/Cargo.toml b/substrate/client/network/bitswap/Cargo.toml index 9982ef80cf6de65885e8089159bfb95fd5eafd73..7ef3ea212427848510e2e4b910efbe3d54e01a48 100644 --- a/substrate/client/network/bitswap/Cargo.toml +++ b/substrate/client/network/bitswap/Cargo.toml @@ -23,9 +23,9 @@ async-channel = "1.8.0" cid = "0.9.0" futures = "0.3.21" libp2p-identity = { version = "0.1.3", features = ["peerid"] } -log = "0.4.17" +log = { workspace = true, default-features = true } prost = "0.12" -thiserror = "1.0" +thiserror = { workspace = true } unsigned-varint = { version = "0.7.1", features = ["asynchronous_codec", "futures"] } sc-client-api = { path = "../../api" } sc-network = { path = ".." } diff --git a/substrate/client/network/light/Cargo.toml b/substrate/client/network/light/Cargo.toml index efefc6f18b6b70c847af334d4e99cbc0dda2a6b5..c757f727fb71a7c2261090d741e286d7851d8239 100644 --- a/substrate/client/network/light/Cargo.toml +++ b/substrate/client/network/light/Cargo.toml @@ -26,11 +26,11 @@ codec = { package = "parity-scale-codec", version = "3.6.1", features = [ ] } futures = "0.3.21" libp2p-identity = { version = "0.1.3", features = ["peerid"] } -log = "0.4.16" +log = { workspace = true, default-features = true } prost = "0.12" sp-blockchain = { path = "../../../primitives/blockchain" } sc-client-api = { path = "../../api" } sc-network = { path = ".." } sp-core = { path = "../../../primitives/core" } sp-runtime = { path = "../../../primitives/runtime" } -thiserror = "1.0" +thiserror = { workspace = true } diff --git a/substrate/client/network/statement/Cargo.toml b/substrate/client/network/statement/Cargo.toml index 0a0ce61527d1b349378fbd6f92d0cecde364d363..b6efee5d9d36ae9deffe9ecd4951176c829de0bc 100644 --- a/substrate/client/network/statement/Cargo.toml +++ b/substrate/client/network/statement/Cargo.toml @@ -21,7 +21,7 @@ async-channel = "1.8.0" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-network-common = { path = "../common" } sc-network-sync = { path = "../sync" } diff --git a/substrate/client/network/sync/Cargo.toml b/substrate/client/network/sync/Cargo.toml index f81b4ee77bdf4b4babbede1936d65c19bc784cb9..32ba3b6356c0ceb0dcf38bda7d333ffddf67d238 100644 --- a/substrate/client/network/sync/Cargo.toml +++ b/substrate/client/network/sync/Cargo.toml @@ -26,12 +26,12 @@ codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive futures = "0.3.21" futures-timer = "3.0.2" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } mockall = "0.11.3" prost = "0.12" schnellru = "0.2.1" smallvec = "1.11.0" -thiserror = "1.0" +thiserror = { workspace = true } tokio-stream = "0.1.14" tokio = { version = "1.32.0", features = ["macros", "time"] } fork-tree = { path = "../../../utils/fork-tree" } diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 7486c091ebf13d1fef6422ab193cebd2b4dd24f9..c1a7009eeb01745ca5a7069eb2a258768ad2c49c 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -33,7 +33,7 @@ use crate::{ }, strategy::{ warp::{EncodedProof, WarpProofRequest, WarpSyncParams}, - SyncingAction, SyncingConfig, SyncingStrategy, + StrategyKey, SyncingAction, SyncingConfig, SyncingStrategy, }, types::{ BadPeer, ExtendedPeerInfo, OpaqueStateRequest, OpaqueStateResponse, PeerRequest, SyncEvent, @@ -48,7 +48,7 @@ use futures::{ FutureExt, StreamExt, }; use libp2p::{request_response::OutboundFailure, PeerId}; -use log::{debug, error, trace}; +use log::{debug, error, trace, warn}; use prometheus_endpoint::{ register, Counter, Gauge, MetricSource, Opts, PrometheusError, Registry, SourcedGauge, U64, }; @@ -214,9 +214,6 @@ pub struct SyncingEngine { /// Syncing strategy. strategy: SyncingStrategy, - /// Syncing configuration for startegies. - syncing_config: SyncingConfig, - /// Blockchain client. client: Arc, @@ -441,8 +438,7 @@ where .map_or(futures::future::pending().boxed().fuse(), |rx| rx.boxed().fuse()); // Initialize syncing strategy. - let strategy = - SyncingStrategy::new(syncing_config.clone(), client.clone(), warp_sync_config)?; + let strategy = SyncingStrategy::new(syncing_config, client.clone(), warp_sync_config)?; let block_announce_protocol_name = block_announce_config.protocol_name().clone(); let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync", 100_000); @@ -471,7 +467,6 @@ where roles, client, strategy, - syncing_config, network_service, peers: HashMap::new(), block_announce_data_cache: LruMap::new(ByLength::new(cache_capacity)), @@ -661,8 +656,15 @@ where Some(event) => self.process_notification_event(event), None => return, }, - warp_target_block_header = &mut self.warp_sync_target_block_header_rx_fused => - self.pass_warp_sync_target_block_header(warp_target_block_header), + warp_target_block_header = &mut self.warp_sync_target_block_header_rx_fused => { + if let Err(_) = self.pass_warp_sync_target_block_header(warp_target_block_header) { + error!( + target: LOG_TARGET, + "Failed to set warp sync target block header, terminating `SyncingEngine`.", + ); + return + } + }, response_event = self.pending_responses.select_next_some() => self.process_response_event(response_event), validation_result = self.block_announce_validator.select_next_some() => @@ -675,48 +677,61 @@ where // Process actions requested by a syncing strategy. if let Err(e) = self.process_strategy_actions() { - error!("Terminating `SyncingEngine` due to fatal error: {e:?}"); + error!( + target: LOG_TARGET, + "Terminating `SyncingEngine` due to fatal error: {e:?}.", + ); return } } } fn process_strategy_actions(&mut self) -> Result<(), ClientError> { - for action in self.strategy.actions() { + for action in self.strategy.actions()? { match action { - SyncingAction::SendBlockRequest { peer_id, request } => { + SyncingAction::SendBlockRequest { peer_id, key, request } => { // Sending block request implies dropping obsolete pending response as we are // not interested in it anymore (see [`SyncingAction::SendBlockRequest`]). - // Furthermore, only one request at a time is allowed to any peer. - let removed = self.pending_responses.remove(&peer_id); - self.send_block_request(peer_id, request.clone()); - - trace!( - target: LOG_TARGET, - "Processed `ChainSyncAction::SendBlockRequest` to {} with {:?}, stale response removed: {}.", - peer_id, - request, - removed, - ) + let removed = self.pending_responses.remove(peer_id, key); + self.send_block_request(peer_id, key, request.clone()); + + if removed { + warn!( + target: LOG_TARGET, + "Processed `ChainSyncAction::SendBlockRequest` to {} from {:?} with {:?}. \ + Stale response removed!", + peer_id, + key, + request, + ) + } else { + trace!( + target: LOG_TARGET, + "Processed `ChainSyncAction::SendBlockRequest` to {} from {:?} with {:?}.", + peer_id, + key, + request, + ) + } }, - SyncingAction::CancelBlockRequest { peer_id } => { - let removed = self.pending_responses.remove(&peer_id); + SyncingAction::CancelRequest { peer_id, key } => { + let removed = self.pending_responses.remove(peer_id, key); trace!( target: LOG_TARGET, "Processed {action:?}, response removed: {removed}.", ); }, - SyncingAction::SendStateRequest { peer_id, request } => { - self.send_state_request(peer_id, request); + SyncingAction::SendStateRequest { peer_id, key, request } => { + self.send_state_request(peer_id, key, request); trace!( target: LOG_TARGET, - "Processed `ChainSyncAction::SendBlockRequest` to {peer_id}.", + "Processed `ChainSyncAction::SendStateRequest` to {peer_id}.", ); }, - SyncingAction::SendWarpProofRequest { peer_id, request } => { - self.send_warp_proof_request(peer_id, request.clone()); + SyncingAction::SendWarpProofRequest { peer_id, key, request } => { + self.send_warp_proof_request(peer_id, key, request.clone()); trace!( target: LOG_TARGET, @@ -726,7 +741,7 @@ where ); }, SyncingAction::DropPeer(BadPeer(peer_id, rep)) => { - self.pending_responses.remove(&peer_id); + self.pending_responses.remove_all(&peer_id); self.network_service .disconnect_peer(peer_id, self.block_announce_protocol_name.clone()); self.network_service.report_peer(peer_id, rep); @@ -753,20 +768,8 @@ where number, ) }, - SyncingAction::Finished => { - let connected_peers = self.peers.iter().filter_map(|(peer_id, peer)| { - peer.info.roles.is_full().then_some(( - *peer_id, - peer.info.best_hash, - peer.info.best_number, - )) - }); - self.strategy.switch_to_next( - self.syncing_config.clone(), - self.client.clone(), - connected_peers, - )?; - }, + // Nothing to do, this is handled internally by `SyncingStrategy`. + SyncingAction::Finished => {}, } } @@ -948,23 +951,18 @@ where } } - fn pass_warp_sync_target_block_header(&mut self, header: Result) { + fn pass_warp_sync_target_block_header( + &mut self, + header: Result, + ) -> Result<(), ()> { match header { - Ok(header) => - if let SyncingStrategy::WarpSyncStrategy(warp_sync) = &mut self.strategy { - warp_sync.set_target_block(header); - } else { - error!( - target: LOG_TARGET, - "Cannot set warp sync target block: no warp sync strategy is active." - ); - debug_assert!(false); - }, + Ok(header) => self.strategy.set_warp_sync_target_block_header(header), Err(err) => { error!( target: LOG_TARGET, "Failed to get target block for warp sync. Error: {err:?}", ); + Err(()) }, } } @@ -1002,7 +1000,7 @@ where } self.strategy.remove_peer(&peer_id); - self.pending_responses.remove(&peer_id); + self.pending_responses.remove_all(&peer_id); self.event_streams .retain(|stream| stream.unbounded_send(SyncEvent::PeerDisconnected(peer_id)).is_ok()); } @@ -1167,7 +1165,7 @@ where Ok(()) } - fn send_block_request(&mut self, peer_id: PeerId, request: BlockRequest) { + fn send_block_request(&mut self, peer_id: PeerId, key: StrategyKey, request: BlockRequest) { if !self.peers.contains_key(&peer_id) { trace!(target: LOG_TARGET, "Cannot send block request to unknown peer {peer_id}"); debug_assert!(false); @@ -1178,12 +1176,18 @@ where self.pending_responses.insert( peer_id, + key, PeerRequest::Block(request.clone()), async move { downloader.download_blocks(peer_id, request).await }.boxed(), ); } - fn send_state_request(&mut self, peer_id: PeerId, request: OpaqueStateRequest) { + fn send_state_request( + &mut self, + peer_id: PeerId, + key: StrategyKey, + request: OpaqueStateRequest, + ) { if !self.peers.contains_key(&peer_id) { trace!(target: LOG_TARGET, "Cannot send state request to unknown peer {peer_id}"); debug_assert!(false); @@ -1192,7 +1196,7 @@ where let (tx, rx) = oneshot::channel(); - self.pending_responses.insert(peer_id, PeerRequest::State, rx.boxed()); + self.pending_responses.insert(peer_id, key, PeerRequest::State, rx.boxed()); match Self::encode_state_request(&request) { Ok(data) => { @@ -1213,7 +1217,12 @@ where } } - fn send_warp_proof_request(&mut self, peer_id: PeerId, request: WarpProofRequest) { + fn send_warp_proof_request( + &mut self, + peer_id: PeerId, + key: StrategyKey, + request: WarpProofRequest, + ) { if !self.peers.contains_key(&peer_id) { trace!(target: LOG_TARGET, "Cannot send warp proof request to unknown peer {peer_id}"); debug_assert!(false); @@ -1222,7 +1231,7 @@ where let (tx, rx) = oneshot::channel(); - self.pending_responses.insert(peer_id, PeerRequest::WarpProof, rx.boxed()); + self.pending_responses.insert(peer_id, key, PeerRequest::WarpProof, rx.boxed()); match &self.warp_sync_protocol_name { Some(name) => self.network_service.start_request( @@ -1259,14 +1268,14 @@ where } fn process_response_event(&mut self, response_event: ResponseEvent) { - let ResponseEvent { peer_id, request, response } = response_event; + let ResponseEvent { peer_id, key, request, response } = response_event; match response { Ok(Ok((resp, _))) => match request { PeerRequest::Block(req) => { match self.block_downloader.block_response_into_blocks(&req, resp) { Ok(blocks) => { - self.strategy.on_block_response(peer_id, req, blocks); + self.strategy.on_block_response(peer_id, key, req, blocks); }, Err(BlockResponseError::DecodeFailed(e)) => { debug!( @@ -1311,10 +1320,10 @@ where }, }; - self.strategy.on_state_response(peer_id, response); + self.strategy.on_state_response(peer_id, key, response); }, PeerRequest::WarpProof => { - self.strategy.on_warp_proof_response(&peer_id, EncodedProof(resp)); + self.strategy.on_warp_proof_response(&peer_id, key, EncodedProof(resp)); }, }, Ok(Err(e)) => { diff --git a/substrate/client/network/sync/src/extra_requests.rs b/substrate/client/network/sync/src/justification_requests.rs similarity index 98% rename from substrate/client/network/sync/src/extra_requests.rs rename to substrate/client/network/sync/src/justification_requests.rs index cd3008d270b1f8b87b186afa4566536216714769..799b6df5831a5783038de15fab2b2eff091d64d9 100644 --- a/substrate/client/network/sync/src/extra_requests.rs +++ b/substrate/client/network/sync/src/justification_requests.rs @@ -16,6 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +//! Justification requests scheduling. [`ExtraRequests`] manages requesting justifications +//! from peers taking into account forks and their finalization (dropping pending requests +//! that don't make sense after one of the forks is finalized). + use crate::{ request_metrics::Metrics, strategy::chain_sync::{PeerSync, PeerSyncState}, diff --git a/substrate/client/network/sync/src/lib.rs b/substrate/client/network/sync/src/lib.rs index 494e3b87aa95514d6377c413cdc8c850c0d06258..e23a23e735d3e25b60b6b5bda8dd4beadcfe9377 100644 --- a/substrate/client/network/sync/src/lib.rs +++ b/substrate/client/network/sync/src/lib.rs @@ -23,8 +23,8 @@ pub use strategy::warp::{WarpSyncParams, WarpSyncPhase, WarpSyncProgress}; pub use types::{SyncEvent, SyncEventStream, SyncState, SyncStatus, SyncStatusProvider}; mod block_announce_validator; -mod extra_requests; mod futures_stream; +mod justification_requests; mod pending_responses; mod request_metrics; mod schema; diff --git a/substrate/client/network/sync/src/pending_responses.rs b/substrate/client/network/sync/src/pending_responses.rs index 21e409eb847fe600d07343cc22533af8cf5d8c36..602c69df7ff96b80c9f176dd7646d843f2c63937 100644 --- a/substrate/client/network/sync/src/pending_responses.rs +++ b/substrate/client/network/sync/src/pending_responses.rs @@ -19,7 +19,7 @@ //! [`PendingResponses`] is responsible for keeping track of pending responses and //! polling them. [`Stream`] implemented by [`PendingResponses`] never terminates. -use crate::{types::PeerRequest, LOG_TARGET}; +use crate::{strategy::StrategyKey, types::PeerRequest, LOG_TARGET}; use futures::{ channel::oneshot, future::BoxFuture, @@ -42,6 +42,7 @@ type ResponseFuture = BoxFuture<'static, ResponseResult>; /// An event we receive once a pending response future resolves. pub(crate) struct ResponseEvent { pub peer_id: PeerId, + pub key: StrategyKey, pub request: PeerRequest, pub response: ResponseResult, } @@ -49,7 +50,8 @@ pub(crate) struct ResponseEvent { /// Stream taking care of polling pending responses. pub(crate) struct PendingResponses { /// Pending responses - pending_responses: StreamMap, ResponseResult)>>, + pending_responses: + StreamMap<(PeerId, StrategyKey), BoxStream<'static, (PeerRequest, ResponseResult)>>, /// Waker to implement never terminating stream waker: Option, } @@ -62,6 +64,7 @@ impl PendingResponses { pub fn insert( &mut self, peer_id: PeerId, + key: StrategyKey, request: PeerRequest, response_future: ResponseFuture, ) { @@ -70,7 +73,7 @@ impl PendingResponses { if self .pending_responses .insert( - peer_id, + (peer_id, key), Box::pin(async move { (request, response_future.await) }.into_stream()), ) .is_some() @@ -87,8 +90,20 @@ impl PendingResponses { } } - pub fn remove(&mut self, peer_id: &PeerId) -> bool { - self.pending_responses.remove(peer_id).is_some() + pub fn remove(&mut self, peer_id: PeerId, key: StrategyKey) -> bool { + self.pending_responses.remove(&(peer_id, key)).is_some() + } + + pub fn remove_all(&mut self, peer_id: &PeerId) { + let to_remove = self + .pending_responses + .keys() + .filter(|(peer, _key)| peer == peer_id) + .cloned() + .collect::>(); + to_remove.iter().for_each(|k| { + self.pending_responses.remove(k); + }); } pub fn len(&self) -> usize { @@ -104,13 +119,13 @@ impl Stream for PendingResponses { cx: &mut Context<'_>, ) -> Poll> { match self.pending_responses.poll_next_unpin(cx) { - Poll::Ready(Some((peer_id, (request, response)))) => { + Poll::Ready(Some(((peer_id, key), (request, response)))) => { // We need to manually remove the stream, because `StreamMap` doesn't know yet that // it's going to yield `None`, so may not remove it before the next request is made // to the same peer. - self.pending_responses.remove(&peer_id); + self.pending_responses.remove(&(peer_id, key)); - Poll::Ready(Some(ResponseEvent { peer_id, request, response })) + Poll::Ready(Some(ResponseEvent { peer_id, key, request, response })) }, Poll::Ready(None) | Poll::Pending => { self.waker = Some(cx.waker().clone()); diff --git a/substrate/client/network/sync/src/strategy.rs b/substrate/client/network/sync/src/strategy.rs index dbfb4188ec3f346d784c39ca113f28e687230ccc..7d6e6a8d3b8b79e9e926261d298e4fcacf7c714e 100644 --- a/substrate/client/network/sync/src/strategy.rs +++ b/substrate/client/network/sync/src/strategy.rs @@ -30,7 +30,7 @@ use crate::{ }; use chain_sync::{ChainSync, ChainSyncAction, ChainSyncMode}; use libp2p::PeerId; -use log::{error, info}; +use log::{debug, error, info}; use prometheus_endpoint::Registry; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; @@ -41,11 +41,11 @@ use sc_network_common::sync::{ use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; use sp_consensus::BlockOrigin; use sp_runtime::{ - traits::{Block as BlockT, NumberFor}, + traits::{Block as BlockT, Header, NumberFor}, Justifications, }; use state::{StateStrategy, StateStrategyAction}; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use warp::{EncodedProof, WarpProofRequest, WarpSync, WarpSyncAction, WarpSyncConfig}; /// Corresponding `ChainSync` mode. @@ -71,16 +71,27 @@ pub struct SyncingConfig { pub metrics_registry: Option, } +/// The key identifying a specific strategy for responses routing. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum StrategyKey { + /// Warp sync initiated this request. + Warp, + /// State sync initiated this request. + State, + /// `ChainSync` initiated this request. + ChainSync, +} + #[derive(Debug)] pub enum SyncingAction { /// Send block request to peer. Always implies dropping a stale block request to the same peer. - SendBlockRequest { peer_id: PeerId, request: BlockRequest }, - /// Drop stale block request. - CancelBlockRequest { peer_id: PeerId }, + SendBlockRequest { peer_id: PeerId, key: StrategyKey, request: BlockRequest }, /// Send state request to peer. - SendStateRequest { peer_id: PeerId, request: OpaqueStateRequest }, + SendStateRequest { peer_id: PeerId, key: StrategyKey, request: OpaqueStateRequest }, /// Send warp proof request to peer. - SendWarpProofRequest { peer_id: PeerId, request: WarpProofRequest }, + SendWarpProofRequest { peer_id: PeerId, key: StrategyKey, request: WarpProofRequest }, + /// Drop stale request. + CancelRequest { peer_id: PeerId, key: StrategyKey }, /// Peer misbehaved. Disconnect, report it and cancel any requests to it. DropPeer(BadPeer), /// Import blocks. @@ -92,15 +103,75 @@ pub enum SyncingAction { number: NumberFor, justifications: Justifications, }, - /// Syncing strategy has finished. + /// Strategy finished. Nothing to do, this is handled by `SyncingStrategy`. Finished, } +impl SyncingAction { + fn is_finished(&self) -> bool { + matches!(self, SyncingAction::Finished) + } +} + +impl From> for SyncingAction { + fn from(action: WarpSyncAction) -> Self { + match action { + WarpSyncAction::SendWarpProofRequest { peer_id, request } => + SyncingAction::SendWarpProofRequest { peer_id, key: StrategyKey::Warp, request }, + WarpSyncAction::SendBlockRequest { peer_id, request } => + SyncingAction::SendBlockRequest { peer_id, key: StrategyKey::Warp, request }, + WarpSyncAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), + WarpSyncAction::Finished => SyncingAction::Finished, + } + } +} + +impl From> for SyncingAction { + fn from(action: StateStrategyAction) -> Self { + match action { + StateStrategyAction::SendStateRequest { peer_id, request } => + SyncingAction::SendStateRequest { peer_id, key: StrategyKey::State, request }, + StateStrategyAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), + StateStrategyAction::ImportBlocks { origin, blocks } => + SyncingAction::ImportBlocks { origin, blocks }, + StateStrategyAction::Finished => SyncingAction::Finished, + } + } +} + +impl From> for SyncingAction { + fn from(action: ChainSyncAction) -> Self { + match action { + ChainSyncAction::SendBlockRequest { peer_id, request } => + SyncingAction::SendBlockRequest { peer_id, key: StrategyKey::ChainSync, request }, + ChainSyncAction::SendStateRequest { peer_id, request } => + SyncingAction::SendStateRequest { peer_id, key: StrategyKey::ChainSync, request }, + ChainSyncAction::CancelRequest { peer_id } => + SyncingAction::CancelRequest { peer_id, key: StrategyKey::ChainSync }, + ChainSyncAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), + ChainSyncAction::ImportBlocks { origin, blocks } => + SyncingAction::ImportBlocks { origin, blocks }, + ChainSyncAction::ImportJustifications { peer_id, hash, number, justifications } => + SyncingAction::ImportJustifications { peer_id, hash, number, justifications }, + } + } +} + /// Proxy to specific syncing strategies. -pub enum SyncingStrategy { - WarpSyncStrategy(WarpSync), - StateSyncStrategy(StateStrategy), - ChainSyncStrategy(ChainSync), +pub struct SyncingStrategy { + /// Syncing configuration. + config: SyncingConfig, + /// Client used by syncing strategies. + client: Arc, + /// Warp strategy. + warp: Option>, + /// State strategy. + state: Option>, + /// `ChainSync` strategy.` + chain_sync: Option>, + /// Connected peers and their best blocks used to seed a new strategy when switching to it in + /// [`SyncingStrategy::proceed_to_next`]. + peer_best_blocks: HashMap)>, } impl SyncingStrategy @@ -123,37 +194,51 @@ where if let SyncMode::Warp = config.mode { let warp_sync_config = warp_sync_config .expect("Warp sync configuration must be supplied in warp sync mode."); - Ok(Self::WarpSyncStrategy(WarpSync::new(client.clone(), warp_sync_config))) + let warp_sync = WarpSync::new(client.clone(), warp_sync_config); + Ok(Self { + config, + client, + warp: Some(warp_sync), + state: None, + chain_sync: None, + peer_best_blocks: Default::default(), + }) } else { - Ok(Self::ChainSyncStrategy(ChainSync::new( + let chain_sync = ChainSync::new( chain_sync_mode(config.mode), client.clone(), config.max_parallel_downloads, config.max_blocks_per_request, - config.metrics_registry, - )?)) + config.metrics_registry.clone(), + std::iter::empty(), + )?; + Ok(Self { + config, + client, + warp: None, + state: None, + chain_sync: Some(chain_sync), + peer_best_blocks: Default::default(), + }) } } /// Notify that a new peer has connected. pub fn add_peer(&mut self, peer_id: PeerId, best_hash: B::Hash, best_number: NumberFor) { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - strategy.add_peer(peer_id, best_hash, best_number), - SyncingStrategy::StateSyncStrategy(strategy) => - strategy.add_peer(peer_id, best_hash, best_number), - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.add_peer(peer_id, best_hash, best_number), - } + self.peer_best_blocks.insert(peer_id, (best_hash, best_number)); + + self.warp.as_mut().map(|s| s.add_peer(peer_id, best_hash, best_number)); + self.state.as_mut().map(|s| s.add_peer(peer_id, best_hash, best_number)); + self.chain_sync.as_mut().map(|s| s.add_peer(peer_id, best_hash, best_number)); } /// Notify that a peer has disconnected. pub fn remove_peer(&mut self, peer_id: &PeerId) { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => strategy.remove_peer(peer_id), - SyncingStrategy::StateSyncStrategy(strategy) => strategy.remove_peer(peer_id), - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.remove_peer(peer_id), - } + self.warp.as_mut().map(|s| s.remove_peer(peer_id)); + self.state.as_mut().map(|s| s.remove_peer(peer_id)); + self.chain_sync.as_mut().map(|s| s.remove_peer(peer_id)); + + self.peer_best_blocks.remove(peer_id); } /// Submit a validated block announcement. @@ -165,14 +250,31 @@ where peer_id: PeerId, announce: &BlockAnnounce, ) -> Option<(B::Hash, NumberFor)> { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - strategy.on_validated_block_announce(is_best, peer_id, announce), - SyncingStrategy::StateSyncStrategy(strategy) => - strategy.on_validated_block_announce(is_best, peer_id, announce), - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_validated_block_announce(is_best, peer_id, announce), + let new_best = if let Some(ref mut warp) = self.warp { + warp.on_validated_block_announce(is_best, peer_id, announce) + } else if let Some(ref mut state) = self.state { + state.on_validated_block_announce(is_best, peer_id, announce) + } else if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.on_validated_block_announce(is_best, peer_id, announce) + } else { + error!(target: LOG_TARGET, "No syncing strategy is active."); + debug_assert!(false); + Some((announce.header.hash(), *announce.header.number())) + }; + + if let Some(new_best) = new_best { + if let Some(best) = self.peer_best_blocks.get_mut(&peer_id) { + *best = new_best; + } else { + debug!( + target: LOG_TARGET, + "Cannot update `peer_best_blocks` as peer {peer_id} is not known to `Strategy` \ + (already disconnected?)", + ); + } } + + new_best } /// Configure an explicit fork sync request in case external code has detected that there is a @@ -183,40 +285,33 @@ where hash: &B::Hash, number: NumberFor, ) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.set_sync_fork_request(peers, hash, number), + // Fork requests are only handled by `ChainSync`. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.set_sync_fork_request(peers.clone(), hash, number); } } /// Request extra justification. pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.request_justification(hash, number), + // Justifications can only be requested via `ChainSync`. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.request_justification(hash, number); } } /// Clear extra justification requests. pub fn clear_justification_requests(&mut self) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.clear_justification_requests(), + // Justification requests can only be cleared by `ChainSync`. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.clear_justification_requests(); } } /// Report a justification import (successful or not). pub fn on_justification_import(&mut self, hash: B::Hash, number: NumberFor, success: bool) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_justification_import(hash, number, success), + // Only `ChainSync` is interested in justification import. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.on_justification_import(hash, number, success); } } @@ -224,36 +319,65 @@ where pub fn on_block_response( &mut self, peer_id: PeerId, + key: StrategyKey, request: BlockRequest, blocks: Vec>, ) { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - strategy.on_block_response(peer_id, request, blocks), - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_block_response(peer_id, request, blocks), + if let (StrategyKey::Warp, Some(ref mut warp)) = (key, &mut self.warp) { + warp.on_block_response(peer_id, request, blocks); + } else if let (StrategyKey::ChainSync, Some(ref mut chain_sync)) = + (key, &mut self.chain_sync) + { + chain_sync.on_block_response(peer_id, request, blocks); + } else { + error!( + target: LOG_TARGET, + "`on_block_response()` called with unexpected key {key:?} \ + or corresponding strategy is not active.", + ); + debug_assert!(false); } } /// Process state response. - pub fn on_state_response(&mut self, peer_id: PeerId, response: OpaqueStateResponse) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(strategy) => - strategy.on_state_response(peer_id, response), - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_state_response(peer_id, response), + pub fn on_state_response( + &mut self, + peer_id: PeerId, + key: StrategyKey, + response: OpaqueStateResponse, + ) { + if let (StrategyKey::State, Some(ref mut state)) = (key, &mut self.state) { + state.on_state_response(peer_id, response); + } else if let (StrategyKey::ChainSync, Some(ref mut chain_sync)) = + (key, &mut self.chain_sync) + { + chain_sync.on_state_response(peer_id, response); + } else { + error!( + target: LOG_TARGET, + "`on_state_response()` called with unexpected key {key:?} \ + or corresponding strategy is not active.", + ); + debug_assert!(false); } } /// Process warp proof response. - pub fn on_warp_proof_response(&mut self, peer_id: &PeerId, response: EncodedProof) { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - strategy.on_warp_proof_response(peer_id, response), - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(_) => {}, + pub fn on_warp_proof_response( + &mut self, + peer_id: &PeerId, + key: StrategyKey, + response: EncodedProof, + ) { + if let (StrategyKey::Warp, Some(ref mut warp)) = (key, &mut self.warp) { + warp.on_warp_proof_response(peer_id, response); + } else { + error!( + target: LOG_TARGET, + "`on_warp_proof_response()` called with unexpected key {key:?} \ + or warp strategy is not active", + ); + debug_assert!(false); } } @@ -264,226 +388,202 @@ where count: usize, results: Vec<(Result>, BlockImportError>, B::Hash)>, ) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(strategy) => - strategy.on_blocks_processed(imported, count, results), - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_blocks_processed(imported, count, results), + // Only `StateStrategy` and `ChainSync` are interested in block processing notifications. + if let Some(ref mut state) = self.state { + state.on_blocks_processed(imported, count, results); + } else if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.on_blocks_processed(imported, count, results); } } /// Notify a syncing strategy that a block has been finalized. pub fn on_block_finalized(&mut self, hash: &B::Hash, number: NumberFor) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.on_block_finalized(hash, number), + // Only `ChainSync` is interested in block finalization notifications. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.on_block_finalized(hash, number); } } /// Inform sync about a new best imported block. pub fn update_chain_info(&mut self, best_hash: &B::Hash, best_number: NumberFor) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.update_chain_info(best_hash, best_number), + // This is relevant to `ChainSync` only. + if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.update_chain_info(best_hash, best_number); } } // Are we in major sync mode? pub fn is_major_syncing(&self) -> bool { - match self { - SyncingStrategy::WarpSyncStrategy(_) => true, - SyncingStrategy::StateSyncStrategy(_) => true, - SyncingStrategy::ChainSyncStrategy(strategy) => - strategy.status().state.is_major_syncing(), - } + self.warp.is_some() || + self.state.is_some() || + match self.chain_sync { + Some(ref s) => s.status().state.is_major_syncing(), + None => unreachable!("At least one syncing startegy is active; qed"), + } } /// Get the number of peers known to the syncing strategy. pub fn num_peers(&self) -> usize { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => strategy.num_peers(), - SyncingStrategy::StateSyncStrategy(strategy) => strategy.num_peers(), - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.num_peers(), - } + self.peer_best_blocks.len() } /// Returns the current sync status. pub fn status(&self) -> SyncStatus { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => strategy.status(), - SyncingStrategy::StateSyncStrategy(strategy) => strategy.status(), - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.status(), + // This function presumes that startegies are executed serially and must be refactored + // once we have parallel strategies. + if let Some(ref warp) = self.warp { + warp.status() + } else if let Some(ref state) = self.state { + state.status() + } else if let Some(ref chain_sync) = self.chain_sync { + chain_sync.status() + } else { + unreachable!("At least one syncing startegy is always active; qed") } } /// Get the total number of downloaded blocks. pub fn num_downloaded_blocks(&self) -> usize { - match self { - SyncingStrategy::WarpSyncStrategy(_) => 0, - SyncingStrategy::StateSyncStrategy(_) => 0, - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.num_downloaded_blocks(), - } + self.chain_sync + .as_ref() + .map_or(0, |chain_sync| chain_sync.num_downloaded_blocks()) } /// Get an estimate of the number of parallel sync requests. pub fn num_sync_requests(&self) -> usize { - match self { - SyncingStrategy::WarpSyncStrategy(_) => 0, - SyncingStrategy::StateSyncStrategy(_) => 0, - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.num_sync_requests(), - } + self.chain_sync.as_ref().map_or(0, |chain_sync| chain_sync.num_sync_requests()) } /// Report Prometheus metrics pub fn report_metrics(&self) { - match self { - SyncingStrategy::WarpSyncStrategy(_) => {}, - SyncingStrategy::StateSyncStrategy(_) => {}, - SyncingStrategy::ChainSyncStrategy(strategy) => strategy.report_metrics(), + if let Some(ref chain_sync) = self.chain_sync { + chain_sync.report_metrics(); + } + } + + /// Let `WarpSync` know about target block header + pub fn set_warp_sync_target_block_header( + &mut self, + target_header: B::Header, + ) -> Result<(), ()> { + match self.warp { + Some(ref mut warp) => { + warp.set_target_block(target_header); + Ok(()) + }, + None => { + error!( + target: LOG_TARGET, + "Cannot set warp sync target block: no warp sync strategy is active." + ); + debug_assert!(false); + Err(()) + }, } } /// Get actions that should be performed by the owner on the strategy's behalf #[must_use] - pub fn actions(&mut self) -> Box>> { - match self { - SyncingStrategy::WarpSyncStrategy(strategy) => - Box::new(strategy.actions().map(|action| match action { - WarpSyncAction::SendWarpProofRequest { peer_id, request } => - SyncingAction::SendWarpProofRequest { peer_id, request }, - WarpSyncAction::SendBlockRequest { peer_id, request } => - SyncingAction::SendBlockRequest { peer_id, request }, - WarpSyncAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), - WarpSyncAction::Finished => SyncingAction::Finished, - })), - SyncingStrategy::StateSyncStrategy(strategy) => - Box::new(strategy.actions().map(|action| match action { - StateStrategyAction::SendStateRequest { peer_id, request } => - SyncingAction::SendStateRequest { peer_id, request }, - StateStrategyAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), - StateStrategyAction::ImportBlocks { origin, blocks } => - SyncingAction::ImportBlocks { origin, blocks }, - StateStrategyAction::Finished => SyncingAction::Finished, - })), - SyncingStrategy::ChainSyncStrategy(strategy) => - Box::new(strategy.actions().map(|action| match action { - ChainSyncAction::SendBlockRequest { peer_id, request } => - SyncingAction::SendBlockRequest { peer_id, request }, - ChainSyncAction::CancelBlockRequest { peer_id } => - SyncingAction::CancelBlockRequest { peer_id }, - ChainSyncAction::SendStateRequest { peer_id, request } => - SyncingAction::SendStateRequest { peer_id, request }, - ChainSyncAction::DropPeer(bad_peer) => SyncingAction::DropPeer(bad_peer), - ChainSyncAction::ImportBlocks { origin, blocks } => - SyncingAction::ImportBlocks { origin, blocks }, - ChainSyncAction::ImportJustifications { - peer_id, - hash, - number, - justifications, - } => SyncingAction::ImportJustifications { - peer_id, - hash, - number, - justifications, - }, - })), + pub fn actions(&mut self) -> Result>, ClientError> { + // This function presumes that strategies are executed serially and must be refactored once + // we have parallel strategies. + let actions: Vec<_> = if let Some(ref mut warp) = self.warp { + warp.actions().map(Into::into).collect() + } else if let Some(ref mut state) = self.state { + state.actions().map(Into::into).collect() + } else if let Some(ref mut chain_sync) = self.chain_sync { + chain_sync.actions().map(Into::into).collect() + } else { + unreachable!("At least one syncing strategy is always active; qed") + }; + + if actions.iter().any(SyncingAction::is_finished) { + self.proceed_to_next()?; } + + Ok(actions) } - /// Switch to next strategy if the active one finished. - pub fn switch_to_next( - &mut self, - config: SyncingConfig, - client: Arc, - connected_peers: impl Iterator)>, - ) -> Result<(), ClientError> { - match self { - Self::WarpSyncStrategy(warp_sync) => { - match warp_sync.take_result() { - Some(res) => { - info!( - target: LOG_TARGET, - "Warp sync is complete, continuing with state sync." - ); - let state_sync = StateStrategy::new( - client, - res.target_header, - res.target_body, - res.target_justifications, - // skip proofs, only set to `true` in `FastUnsafe` sync mode - false, - connected_peers - .map(|(peer_id, _best_hash, best_number)| (peer_id, best_number)), - ); - - *self = Self::StateSyncStrategy(state_sync); - }, - None => { - error!( - target: LOG_TARGET, - "Warp sync failed. Falling back to full sync." - ); - let mut chain_sync = match ChainSync::new( - chain_sync_mode(config.mode), - client, - config.max_parallel_downloads, - config.max_blocks_per_request, - config.metrics_registry, - ) { - Ok(chain_sync) => chain_sync, - Err(e) => { - error!(target: LOG_TARGET, "Failed to start `ChainSync`."); - return Err(e) - }, - }; - // Let `ChainSync` know about connected peers. - connected_peers.into_iter().for_each( - |(peer_id, best_hash, best_number)| { - chain_sync.add_peer(peer_id, best_hash, best_number) - }, - ); - - *self = Self::ChainSyncStrategy(chain_sync); - }, - } - }, - Self::StateSyncStrategy(state_sync) => { - if state_sync.is_succeded() { - info!(target: LOG_TARGET, "State sync is complete, continuing with block sync."); - } else { - error!(target: LOG_TARGET, "State sync failed. Falling back to full sync."); - } - let mut chain_sync = match ChainSync::new( - chain_sync_mode(config.mode), - client, - config.max_parallel_downloads, - config.max_blocks_per_request, - config.metrics_registry, - ) { - Ok(chain_sync) => chain_sync, - Err(e) => { - error!(target: LOG_TARGET, "Failed to start `ChainSync`."); - return Err(e) - }, - }; - // Let `ChainSync` know about connected peers. - connected_peers.into_iter().for_each(|(peer_id, best_hash, best_number)| { - chain_sync.add_peer(peer_id, best_hash, best_number) - }); - - *self = Self::ChainSyncStrategy(chain_sync); - }, - Self::ChainSyncStrategy(_) => { - error!(target: LOG_TARGET, "`ChainSyncStrategy` is final startegy, cannot switch to next."); - debug_assert!(false); - }, + /// Proceed with the next strategy if the active one finished. + pub fn proceed_to_next(&mut self) -> Result<(), ClientError> { + // The strategies are switched as `WarpSync` -> `StateStartegy` -> `ChainSync`. + if let Some(ref mut warp) = self.warp { + match warp.take_result() { + Some(res) => { + info!( + target: LOG_TARGET, + "Warp sync is complete, continuing with state sync." + ); + let state_sync = StateStrategy::new( + self.client.clone(), + res.target_header, + res.target_body, + res.target_justifications, + false, + self.peer_best_blocks + .iter() + .map(|(peer_id, (_, best_number))| (*peer_id, *best_number)), + ); + + self.warp = None; + self.state = Some(state_sync); + Ok(()) + }, + None => { + error!( + target: LOG_TARGET, + "Warp sync failed. Continuing with full sync." + ); + let chain_sync = match ChainSync::new( + chain_sync_mode(self.config.mode), + self.client.clone(), + self.config.max_parallel_downloads, + self.config.max_blocks_per_request, + self.config.metrics_registry.clone(), + self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { + (*peer_id, *best_hash, *best_number) + }), + ) { + Ok(chain_sync) => chain_sync, + Err(e) => { + error!(target: LOG_TARGET, "Failed to start `ChainSync`."); + return Err(e) + }, + }; + + self.warp = None; + self.chain_sync = Some(chain_sync); + Ok(()) + }, + } + } else if let Some(state) = &self.state { + if state.is_succeded() { + info!(target: LOG_TARGET, "State sync is complete, continuing with block sync."); + } else { + error!(target: LOG_TARGET, "State sync failed. Falling back to full sync."); + } + let chain_sync = match ChainSync::new( + chain_sync_mode(self.config.mode), + self.client.clone(), + self.config.max_parallel_downloads, + self.config.max_blocks_per_request, + self.config.metrics_registry.clone(), + self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| { + (*peer_id, *best_hash, *best_number) + }), + ) { + Ok(chain_sync) => chain_sync, + Err(e) => { + error!(target: LOG_TARGET, "Failed to start `ChainSync`."); + return Err(e); + }, + }; + + self.state = None; + self.chain_sync = Some(chain_sync); + Ok(()) + } else { + unreachable!("Only warp & state strategies can finish; qed") } - Ok(()) } } diff --git a/substrate/client/network/sync/src/strategy/chain_sync.rs b/substrate/client/network/sync/src/strategy/chain_sync.rs index 62c260d582b5a75bf38c81b9c5b3f5e65df1e8a4..ad0c75363e78ac948b4e10352328c64cefb792a1 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync.rs @@ -30,7 +30,7 @@ use crate::{ blocks::BlockCollection, - extra_requests::ExtraRequests, + justification_requests::ExtraRequests, schema::v1::StateResponse, strategy::{ state_sync::{ImportResult, StateSync, StateSyncProvider}, @@ -212,10 +212,10 @@ struct GapSync { pub enum ChainSyncAction { /// Send block request to peer. Always implies dropping a stale block request to the same peer. SendBlockRequest { peer_id: PeerId, request: BlockRequest }, - /// Drop stale block request. - CancelBlockRequest { peer_id: PeerId }, /// Send state request to peer. SendStateRequest { peer_id: PeerId, request: OpaqueStateRequest }, + /// Drop stale request. + CancelRequest { peer_id: PeerId }, /// Peer misbehaved. Disconnect, report it and cancel the block request to it. DropPeer(BadPeer), /// Import blocks. @@ -373,6 +373,7 @@ where max_parallel_downloads: u32, max_blocks_per_request: u32, metrics_registry: Option, + initial_peers: impl Iterator)>, ) -> Result { let mut sync = Self { client, @@ -405,6 +406,10 @@ where }; sync.reset_sync_start_point()?; + initial_peers.for_each(|(peer_id, best_hash, best_number)| { + sync.add_peer(peer_id, best_hash, best_number); + }); + Ok(sync) } @@ -1312,34 +1317,35 @@ where ); let old_peers = std::mem::take(&mut self.peers); - old_peers.into_iter().for_each(|(peer_id, mut p)| { - // peers that were downloading justifications - // should be kept in that state. - if let PeerSyncState::DownloadingJustification(_) = p.state { - // We make sure our commmon number is at least something we have. - trace!( - target: LOG_TARGET, - "Keeping peer {} after restart, updating common number from={} => to={} (our best).", - peer_id, - p.common_number, - self.best_queued_number, - ); - p.common_number = self.best_queued_number; - self.peers.insert(peer_id, p); - return + old_peers.into_iter().for_each(|(peer_id, mut peer_sync)| { + match peer_sync.state { + PeerSyncState::Available => { + self.add_peer(peer_id, peer_sync.best_hash, peer_sync.best_number); + }, + PeerSyncState::AncestorSearch { .. } | + PeerSyncState::DownloadingNew(_) | + PeerSyncState::DownloadingStale(_) | + PeerSyncState::DownloadingGap(_) | + PeerSyncState::DownloadingState => { + // Cancel a request first, as `add_peer` may generate a new request. + self.actions.push(ChainSyncAction::CancelRequest { peer_id }); + self.add_peer(peer_id, peer_sync.best_hash, peer_sync.best_number); + }, + PeerSyncState::DownloadingJustification(_) => { + // Peers that were downloading justifications + // should be kept in that state. + // We make sure our commmon number is at least something we have. + trace!( + target: LOG_TARGET, + "Keeping peer {} after restart, updating common number from={} => to={} (our best).", + peer_id, + peer_sync.common_number, + self.best_queued_number, + ); + peer_sync.common_number = self.best_queued_number; + self.peers.insert(peer_id, peer_sync); + }, } - - // handle peers that were in other states. - let action = match self.add_peer_inner(peer_id, p.best_hash, p.best_number) { - // since the request is not a justification, remove it from pending responses - Ok(None) => ChainSyncAction::CancelBlockRequest { peer_id }, - // update the request if the new one is available - Ok(Some(request)) => ChainSyncAction::SendBlockRequest { peer_id, request }, - // this implies that we need to drop pending response from the peer - Err(bad_peer) => ChainSyncAction::DropPeer(bad_peer), - }; - - self.actions.push(action); }); } diff --git a/substrate/client/network/sync/src/strategy/chain_sync/test.rs b/substrate/client/network/sync/src/strategy/chain_sync/test.rs index c89096bc6c9045ddb42eb7e5bb65485d7a90f851..127b6862f0e0dca6c01a88bc6ee83b1f57e7c4dc 100644 --- a/substrate/client/network/sync/src/strategy/chain_sync/test.rs +++ b/substrate/client/network/sync/src/strategy/chain_sync/test.rs @@ -38,7 +38,9 @@ fn processes_empty_response_on_justification_request_for_unknown_block() { let client = Arc::new(TestClientBuilder::new().build()); let peer_id = PeerId::random(); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) + .unwrap(); let (a1_hash, a1_number) = { let a1 = BlockBuilderBuilder::new(&*client) @@ -91,7 +93,11 @@ fn processes_empty_response_on_justification_request_for_unknown_block() { fn restart_doesnt_affect_peers_downloading_finality_data() { let mut client = Arc::new(TestClientBuilder::new().build()); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None).unwrap(); + // we request max 8 blocks to always initiate block requests to both peers for the test to be + // deterministic + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 8, None, std::iter::empty()) + .unwrap(); let peer_id1 = PeerId::random(); let peer_id2 = PeerId::random(); @@ -122,10 +128,13 @@ fn restart_doesnt_affect_peers_downloading_finality_data() { // we wil send block requests to these peers // for these blocks we don't know about - assert!(sync - .block_requests() - .into_iter() - .all(|(p, _)| { p == peer_id1 || p == peer_id2 })); + let actions = sync.actions().collect::>(); + assert_eq!(actions.len(), 2); + assert!(actions.iter().all(|action| match action { + ChainSyncAction::SendBlockRequest { peer_id, .. } => + peer_id == &peer_id1 || peer_id == &peer_id2, + _ => false, + })); // add a new peer at a known block sync.add_peer(peer_id3, b1_hash, b1_number); @@ -146,22 +155,29 @@ fn restart_doesnt_affect_peers_downloading_finality_data() { PeerSyncState::DownloadingJustification(b1_hash), ); - // clear old actions + // drop old actions let _ = sync.take_actions(); // we restart the sync state sync.restart(); - let actions = sync.take_actions().collect::>(); - // which should make us send out block requests to the first two peers - assert_eq!(actions.len(), 2); + // which should make us cancel and send out again block requests to the first two peers + let actions = sync.actions().collect::>(); + assert_eq!(actions.len(), 4); + let mut cancelled_first = HashSet::new(); assert!(actions.iter().all(|action| match action { - ChainSyncAction::SendBlockRequest { peer_id, .. } => - peer_id == &peer_id1 || peer_id == &peer_id2, + ChainSyncAction::CancelRequest { peer_id, .. } => { + cancelled_first.insert(peer_id); + peer_id == &peer_id1 || peer_id == &peer_id2 + }, + ChainSyncAction::SendBlockRequest { peer_id, .. } => { + assert!(cancelled_first.remove(peer_id)); + peer_id == &peer_id1 || peer_id == &peer_id2 + }, _ => false, })); - // peer 3 should be unaffected it was downloading finality data + // peer 3 should be unaffected as it was downloading finality data assert_eq!( sync.peers.get(&peer_id3).unwrap().state, PeerSyncState::DownloadingJustification(b1_hash), @@ -275,7 +291,9 @@ fn do_ancestor_search_when_common_block_to_best_qeued_gap_is_to_big() { let mut client = Arc::new(TestClientBuilder::new().build()); let info = client.info(); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) + .unwrap(); let peer_id1 = PeerId::random(); let peer_id2 = PeerId::random(); @@ -421,7 +439,9 @@ fn can_sync_huge_fork() { let info = client.info(); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) + .unwrap(); let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); let just = (*b"TEST", Vec::new()); @@ -554,7 +574,9 @@ fn syncs_fork_without_duplicate_requests() { let info = client.info(); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) + .unwrap(); let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); let just = (*b"TEST", Vec::new()); @@ -689,7 +711,9 @@ fn removes_target_fork_on_disconnect() { 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(ChainSyncMode::Full, client.clone(), 1, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) + .unwrap(); let peer_id1 = PeerId::random(); let common_block = blocks[1].clone(); @@ -714,7 +738,9 @@ fn can_import_response_with_missing_blocks() { let empty_client = Arc::new(TestClientBuilder::new().build()); - let mut sync = ChainSync::new(ChainSyncMode::Full, empty_client.clone(), 1, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, empty_client.clone(), 1, 64, None, std::iter::empty()) + .unwrap(); let peer_id1 = PeerId::random(); let best_block = blocks[3].clone(); @@ -745,7 +771,9 @@ fn ancestor_search_repeat() { #[test] fn sync_restart_removes_block_but_not_justification_requests() { let mut client = Arc::new(TestClientBuilder::new().build()); - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 1, 64, None, std::iter::empty()) + .unwrap(); let peers = vec![PeerId::random(), PeerId::random()]; @@ -813,7 +841,7 @@ fn sync_restart_removes_block_but_not_justification_requests() { let actions = sync.take_actions().collect::>(); for action in actions.iter() { match action { - ChainSyncAction::CancelBlockRequest { peer_id } => { + ChainSyncAction::CancelRequest { peer_id } => { pending_responses.remove(&peer_id); }, ChainSyncAction::SendBlockRequest { peer_id, .. } => { @@ -887,7 +915,9 @@ fn request_across_forks() { fork_blocks }; - let mut sync = ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None).unwrap(); + let mut sync = + ChainSync::new(ChainSyncMode::Full, client.clone(), 5, 64, None, std::iter::empty()) + .unwrap(); // Add the peers, all at the common ancestor 100. let common_block = blocks.last().unwrap(); diff --git a/substrate/client/network/sync/src/strategy/state.rs b/substrate/client/network/sync/src/strategy/state.rs index ae3f7b6005594d020dfc3f32602833586d16ed92..12d36ff9e01a9c16bfee482c5e9e641b13bb3b87 100644 --- a/substrate/client/network/sync/src/strategy/state.rs +++ b/substrate/client/network/sync/src/strategy/state.rs @@ -330,11 +330,6 @@ impl StateStrategy { } } - /// Get the number of peers known to syncing. - pub fn num_peers(&self) -> usize { - self.peers.len() - } - /// Get actions that should be performed by the owner on [`WarpSync`]'s behalf #[must_use] pub fn actions(&mut self) -> impl Iterator> { diff --git a/substrate/client/network/test/Cargo.toml b/substrate/client/network/test/Cargo.toml index dced6ed673057deb6959eb4b6fb001cff5be301a..4f57287a39cc8da10d3a30f769d7d584e16f0904 100644 --- a/substrate/client/network/test/Cargo.toml +++ b/substrate/client/network/test/Cargo.toml @@ -21,7 +21,7 @@ async-trait = "0.1.74" futures = "0.3.21" futures-timer = "3.0.1" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" rand = "0.8.5" sc-block-builder = { path = "../../block-builder" } diff --git a/substrate/client/network/transactions/Cargo.toml b/substrate/client/network/transactions/Cargo.toml index 9e021059eb3da48eab08803a0b2b69819575d718..01c8ac8814d6aff1448caa18bbe8f3287139553f 100644 --- a/substrate/client/network/transactions/Cargo.toml +++ b/substrate/client/network/transactions/Cargo.toml @@ -20,7 +20,7 @@ array-bytes = "6.1" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" libp2p = "0.51.4" -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus" } sc-network = { path = ".." } sc-network-common = { path = "../common" } diff --git a/substrate/client/offchain/Cargo.toml b/substrate/client/offchain/Cargo.toml index 520523712862d7e21775f5b10a22f20d205ecdba..caa4bb03f40cba37b10444af6820833f8b4a8b84 100644 --- a/substrate/client/offchain/Cargo.toml +++ b/substrate/client/offchain/Cargo.toml @@ -26,7 +26,7 @@ hyper = { version = "0.14.16", features = ["http2", "stream"] } hyper-rustls = { version = "0.24.0", features = ["http2"] } libp2p = "0.51.4" num_cpus = "1.13" -once_cell = "1.8" +once_cell = "1.19" parking_lot = "0.12.1" rand = "0.8.5" threadpool = "1.7" @@ -42,7 +42,7 @@ sp-offchain = { path = "../../primitives/offchain" } sp-runtime = { path = "../../primitives/runtime" } sp-keystore = { path = "../../primitives/keystore" } sp-externalities = { path = "../../primitives/externalities" } -log = "0.4.17" +log = { workspace = true, default-features = true } [dev-dependencies] lazy_static = "1.4.0" diff --git a/substrate/client/proposer-metrics/Cargo.toml b/substrate/client/proposer-metrics/Cargo.toml index 40fcc722010c96b1f8a380d61f0fab6f58b4a485..f560ce2d65e6e6336c7fa372618c858616ca134e 100644 --- a/substrate/client/proposer-metrics/Cargo.toml +++ b/substrate/client/proposer-metrics/Cargo.toml @@ -16,5 +16,5 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } diff --git a/substrate/client/rpc-api/Cargo.toml b/substrate/client/rpc-api/Cargo.toml index 8e781151ba8d83c14adbea03e06dfb94d4b46590..1b7af6a4a52fe650dd325cc38d92daddd9c7a9fa 100644 --- a/substrate/client/rpc-api/Cargo.toml +++ b/substrate/client/rpc-api/Cargo.toml @@ -18,9 +18,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" -thiserror = "1.0" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } sc-chain-spec = { path = "../chain-spec" } sc-mixnet = { path = "../mixnet" } sc-transaction-pool-api = { path = "../transaction-pool/api" } @@ -28,4 +28,4 @@ sp-core = { path = "../../primitives/core" } sp-rpc = { path = "../../primitives/rpc" } sp-runtime = { path = "../../primitives/runtime" } sp-version = { path = "../../primitives/version" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index b624a14e263ad935ecdcce03de0cf3d38c3c07ea..7f7a86799e0cc6510fc4ab7f0d3e1c2c3070b85f 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -16,11 +16,15 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["server"] } -log = "0.4.17" -serde_json = "1.0.111" +jsonrpsee = { version = "0.22", features = ["server"] } +log = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } tokio = { version = "1.22.0", features = ["parking_lot"] } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } tower-http = { version = "0.4.0", features = ["cors"] } tower = { version = "0.4.13", features = ["util"] } http = "0.2.8" +hyper = "0.14.27" +futures = "0.3.29" +pin-project = "1.1.3" +governor = "0.6.0" diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index 5d8da190f6277341d820bad65570f3eabe0e797e..e65d954bed5266746f2c163e865613f255356c44 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -22,21 +22,34 @@ pub mod middleware; -use std::{error::Error as StdError, net::SocketAddr, time::Duration}; +use std::{ + convert::Infallible, error::Error as StdError, net::SocketAddr, num::NonZeroU32, time::Duration, +}; use http::header::HeaderValue; +use hyper::{ + server::conn::AddrStream, + service::{make_service_fn, service_fn}, +}; use jsonrpsee::{ - server::middleware::{HostFilterLayer, ProxyGetRequestLayer}, - RpcModule, + server::{ + middleware::http::{HostFilterLayer, ProxyGetRequestLayer}, + stop_channel, ws, PingConfig, StopHandle, TowerServiceBuilder, + }, + Methods, RpcModule, }; use tokio::net::TcpListener; +use tower::Service; use tower_http::cors::{AllowOrigin, CorsLayer}; -pub use crate::middleware::RpcMetrics; -pub use jsonrpsee::core::{ - id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, - traits::IdProvider, +pub use jsonrpsee::{ + core::{ + id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, + traits::IdProvider, + }, + server::{middleware::rpc::RpcServiceBuilder, BatchRequestConfig}, }; +pub use middleware::{MetricsLayer, RateLimitLayer, RpcMetrics}; const MEGABYTE: u32 = 1024 * 1024; @@ -68,14 +81,31 @@ pub struct Config<'a, M: Send + Sync + 'static> { pub id_provider: Option>, /// Tokio runtime handle. pub tokio_handle: tokio::runtime::Handle, + /// Batch request config. + pub batch_config: BatchRequestConfig, + /// Rate limit calls per minute. + pub rate_limit: Option, +} + +#[derive(Debug, Clone)] +struct PerConnection { + methods: Methods, + stop_handle: StopHandle, + metrics: Option, + tokio_handle: tokio::runtime::Handle, + service_builder: TowerServiceBuilder, } /// Start RPC server listening on given address. -pub async fn start_server( +pub async fn start_server( config: Config<'_, M>, -) -> Result> { +) -> Result> +where + M: Send + Sync, +{ let Config { addrs, + batch_config, cors, max_payload_in_mb, max_payload_out_mb, @@ -86,13 +116,14 @@ pub async fn start_server( id_provider, tokio_handle, rpc_api, + rate_limit, } = config; let std_listener = TcpListener::bind(addrs.as_slice()).await?.into_std()?; let local_addr = std_listener.local_addr().ok(); let host_filter = hosts_filtering(cors.is_some(), local_addr); - let middleware = tower::ServiceBuilder::new() + let http_middleware = tower::ServiceBuilder::new() .option_layer(host_filter) // Proxy `GET /health` requests to internal `system_health` method. .layer(ProxyGetRequestLayer::new("/health", "system_health")?) @@ -103,10 +134,16 @@ pub async fn start_server( .max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE)) .max_connections(max_connections) .max_subscriptions_per_connection(max_subs_per_conn) - .ping_interval(Duration::from_secs(30)) - .set_middleware(middleware) + .enable_ws_ping( + PingConfig::new() + .ping_interval(Duration::from_secs(30)) + .inactive_limit(Duration::from_secs(60)) + .max_failures(3), + ) + .set_http_middleware(http_middleware) .set_message_buffer_capacity(message_buffer_capacity) - .custom_tokio_runtime(tokio_handle); + .set_batch_request_config(batch_config) + .custom_tokio_runtime(tokio_handle.clone()); if let Some(provider) = id_provider { builder = builder.set_id_provider(provider); @@ -114,22 +151,72 @@ pub async fn start_server( builder = builder.set_id_provider(RandomStringIdProvider::new(16)); }; - let rpc_api = build_rpc_api(rpc_api); - let handle = if let Some(metrics) = metrics { - let server = builder.set_logger(metrics).build_from_tcp(std_listener)?; - server.start(rpc_api) - } else { - let server = builder.build_from_tcp(std_listener)?; - server.start(rpc_api) + let (stop_handle, server_handle) = stop_channel(); + let cfg = PerConnection { + methods: build_rpc_api(rpc_api).into(), + service_builder: builder.to_service_builder(), + metrics, + tokio_handle, + stop_handle: stop_handle.clone(), }; + let make_service = make_service_fn(move |_conn: &AddrStream| { + let cfg = cfg.clone(); + + async move { + let cfg = cfg.clone(); + + Ok::<_, Infallible>(service_fn(move |req| { + let PerConnection { service_builder, metrics, tokio_handle, stop_handle, methods } = + cfg.clone(); + + let is_websocket = ws::is_upgrade_request(&req); + let transport_label = if is_websocket { "ws" } else { "http" }; + + let metrics = metrics.map(|m| MetricsLayer::new(m, transport_label)); + let rate_limit = rate_limit.map(|r| RateLimitLayer::per_minute(r)); + + // NOTE: The metrics needs to run first to include rate-limited calls in the + // metrics. + let rpc_middleware = + RpcServiceBuilder::new().option_layer(metrics.clone()).option_layer(rate_limit); + + let mut svc = + service_builder.set_rpc_middleware(rpc_middleware).build(methods, stop_handle); + + async move { + if is_websocket { + let on_disconnect = svc.on_session_closed(); + + // Spawn a task to handle when the connection is closed. + tokio_handle.spawn(async move { + let now = std::time::Instant::now(); + metrics.as_ref().map(|m| m.ws_connect()); + on_disconnect.await; + metrics.as_ref().map(|m| m.ws_disconnect(now)); + }); + } + + svc.call(req).await + } + })) + } + }); + + let server = hyper::Server::from_tcp(std_listener)?.serve(make_service); + + tokio::spawn(async move { + let graceful = server.with_graceful_shutdown(async move { stop_handle.shutdown().await }); + let _ = graceful.await; + }); + log::info!( "Running JSON-RPC server: addr={}, allowed origins={}", local_addr.map_or_else(|| "unknown".to_string(), |a| a.to_string()), format_cors(cors) ); - Ok(handle) + Ok(server_handle) } fn hosts_filtering(enabled: bool, addr: Option) -> Option { diff --git a/substrate/client/rpc-servers/src/middleware.rs b/substrate/client/rpc-servers/src/middleware.rs deleted file mode 100644 index fabb64eafa797189789611e01757aa7e5289b6b2..0000000000000000000000000000000000000000 --- a/substrate/client/rpc-servers/src/middleware.rs +++ /dev/null @@ -1,226 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! RPC middleware to collect prometheus metrics on RPC calls. - -use jsonrpsee::server::logger::{ - HttpRequest, Logger, MethodKind, Params, SuccessOrError, TransportProtocol, -}; -use prometheus_endpoint::{ - register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, - U64, -}; -use std::net::SocketAddr; - -/// Histogram time buckets in microseconds. -const HISTOGRAM_BUCKETS: [f64; 11] = [ - 5.0, - 25.0, - 100.0, - 500.0, - 1_000.0, - 2_500.0, - 10_000.0, - 25_000.0, - 100_000.0, - 1_000_000.0, - 10_000_000.0, -]; - -/// Metrics for RPC middleware storing information about the number of requests started/completed, -/// calls started/completed and their timings. -#[derive(Debug, Clone)] -pub struct RpcMetrics { - /// Number of RPC requests received since the server started. - requests_started: CounterVec, - /// Number of RPC requests completed since the server started. - requests_finished: CounterVec, - /// Histogram over RPC execution times. - calls_time: HistogramVec, - /// Number of calls started. - calls_started: CounterVec, - /// Number of calls completed. - calls_finished: CounterVec, - /// Number of Websocket sessions opened. - ws_sessions_opened: Option>, - /// Number of Websocket sessions closed. - ws_sessions_closed: Option>, -} - -impl RpcMetrics { - /// Create an instance of metrics - pub fn new(metrics_registry: Option<&Registry>) -> Result, PrometheusError> { - if let Some(metrics_registry) = metrics_registry { - Ok(Some(Self { - requests_started: register( - CounterVec::new( - Opts::new( - "substrate_rpc_requests_started", - "Number of RPC requests (not calls) received by the server.", - ), - &["protocol"], - )?, - metrics_registry, - )?, - requests_finished: register( - CounterVec::new( - Opts::new( - "substrate_rpc_requests_finished", - "Number of RPC requests (not calls) processed by the server.", - ), - &["protocol"], - )?, - metrics_registry, - )?, - calls_time: register( - HistogramVec::new( - HistogramOpts::new( - "substrate_rpc_calls_time", - "Total time [μs] of processed RPC calls", - ) - .buckets(HISTOGRAM_BUCKETS.to_vec()), - &["protocol", "method"], - )?, - metrics_registry, - )?, - calls_started: register( - CounterVec::new( - Opts::new( - "substrate_rpc_calls_started", - "Number of received RPC calls (unique un-batched requests)", - ), - &["protocol", "method"], - )?, - metrics_registry, - )?, - calls_finished: register( - CounterVec::new( - Opts::new( - "substrate_rpc_calls_finished", - "Number of processed RPC calls (unique un-batched requests)", - ), - &["protocol", "method", "is_error"], - )?, - metrics_registry, - )?, - ws_sessions_opened: register( - Counter::new( - "substrate_rpc_sessions_opened", - "Number of persistent RPC sessions opened", - )?, - metrics_registry, - )? - .into(), - ws_sessions_closed: register( - Counter::new( - "substrate_rpc_sessions_closed", - "Number of persistent RPC sessions closed", - )?, - metrics_registry, - )? - .into(), - })) - } else { - Ok(None) - } - } -} - -impl Logger for RpcMetrics { - type Instant = std::time::Instant; - - 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()); - } - } - - fn on_request(&self, transport: TransportProtocol) -> Self::Instant { - let transport_label = transport_label_str(transport); - let now = std::time::Instant::now(); - self.requests_started.with_label_values(&[transport_label]).inc(); - now - } - - 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={}", - transport_label, - name, - params, - kind, - ); - self.calls_started.with_label_values(&[transport_label, name]).inc(); - } - - fn on_result( - &self, - name: &str, - success_or_error: SuccessOrError, - 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", - transport_label, - name, - micros, - ); - self.calls_time.with_label_values(&[transport_label, name]).observe(micros as _); - - self.calls_finished - .with_label_values(&[ - transport_label, - name, - // the label "is_error", so `success` should be regarded as false - // and vice-versa to be registrered correctly. - if success_or_error.is_success() { "false" } else { "true" }, - ]) - .inc(); - } - - 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_disconnect(&self, _remote_addr: SocketAddr, transport: TransportProtocol) { - if let TransportProtocol::WebSocket = transport { - self.ws_sessions_closed.as_ref().map(|counter| counter.inc()); - } - } -} - -fn transport_label_str(t: TransportProtocol) -> &'static str { - match t { - TransportProtocol::Http => "http", - TransportProtocol::WebSocket => "ws", - } -} diff --git a/substrate/client/rpc-servers/src/middleware/metrics.rs b/substrate/client/rpc-servers/src/middleware/metrics.rs new file mode 100644 index 0000000000000000000000000000000000000000..c2d1956c3b3985e8e071ad5316dcc2a150410415 --- /dev/null +++ b/substrate/client/rpc-servers/src/middleware/metrics.rs @@ -0,0 +1,281 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! RPC middleware to collect prometheus metrics on RPC calls. + +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + time::Instant, +}; + +use jsonrpsee::{server::middleware::rpc::RpcServiceT, types::Request, MethodResponse}; +use pin_project::pin_project; +use prometheus_endpoint::{ + register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, + U64, +}; + +/// Histogram time buckets in microseconds. +const HISTOGRAM_BUCKETS: [f64; 11] = [ + 5.0, + 25.0, + 100.0, + 500.0, + 1_000.0, + 2_500.0, + 10_000.0, + 25_000.0, + 100_000.0, + 1_000_000.0, + 10_000_000.0, +]; + +/// Metrics for RPC middleware storing information about the number of requests started/completed, +/// calls started/completed and their timings. +#[derive(Debug, Clone)] +pub struct RpcMetrics { + /// Histogram over RPC execution times. + calls_time: HistogramVec, + /// Number of calls started. + calls_started: CounterVec, + /// Number of calls completed. + calls_finished: CounterVec, + /// Number of Websocket sessions opened. + ws_sessions_opened: Option>, + /// Number of Websocket sessions closed. + ws_sessions_closed: Option>, + /// Histogram over RPC websocket sessions. + ws_sessions_time: HistogramVec, +} + +impl RpcMetrics { + /// Create an instance of metrics + pub fn new(metrics_registry: Option<&Registry>) -> Result, PrometheusError> { + if let Some(metrics_registry) = metrics_registry { + Ok(Some(Self { + calls_time: register( + HistogramVec::new( + HistogramOpts::new( + "substrate_rpc_calls_time", + "Total time [μs] of processed RPC calls", + ) + .buckets(HISTOGRAM_BUCKETS.to_vec()), + &["protocol", "method"], + )?, + metrics_registry, + )?, + calls_started: register( + CounterVec::new( + Opts::new( + "substrate_rpc_calls_started", + "Number of received RPC calls (unique un-batched requests)", + ), + &["protocol", "method"], + )?, + metrics_registry, + )?, + calls_finished: register( + CounterVec::new( + Opts::new( + "substrate_rpc_calls_finished", + "Number of processed RPC calls (unique un-batched requests)", + ), + &["protocol", "method", "is_error"], + )?, + metrics_registry, + )?, + ws_sessions_opened: register( + Counter::new( + "substrate_rpc_sessions_opened", + "Number of persistent RPC sessions opened", + )?, + metrics_registry, + )? + .into(), + ws_sessions_closed: register( + Counter::new( + "substrate_rpc_sessions_closed", + "Number of persistent RPC sessions closed", + )?, + metrics_registry, + )? + .into(), + ws_sessions_time: register( + HistogramVec::new( + HistogramOpts::new( + "substrate_rpc_sessions_time", + "Total time [s] for each websocket session", + ) + .buckets(HISTOGRAM_BUCKETS.to_vec()), + &["protocol"], + )?, + metrics_registry, + )?, + })) + } else { + Ok(None) + } + } + + pub(crate) fn ws_connect(&self) { + self.ws_sessions_opened.as_ref().map(|counter| counter.inc()); + } + + pub(crate) fn ws_disconnect(&self, now: Instant) { + let micros = now.elapsed().as_secs(); + + self.ws_sessions_closed.as_ref().map(|counter| counter.inc()); + self.ws_sessions_time.with_label_values(&["ws"]).observe(micros as _); + } +} + +/// Metrics layer. +#[derive(Clone)] +pub struct MetricsLayer { + inner: RpcMetrics, + transport_label: &'static str, +} + +impl MetricsLayer { + /// Create a new [`MetricsLayer`]. + pub fn new(metrics: RpcMetrics, transport_label: &'static str) -> Self { + Self { inner: metrics, transport_label } + } + + pub(crate) fn ws_connect(&self) { + self.inner.ws_connect(); + } + + pub(crate) fn ws_disconnect(&self, now: Instant) { + self.inner.ws_disconnect(now) + } +} + +impl tower::Layer for MetricsLayer { + type Service = Metrics; + + fn layer(&self, inner: S) -> Self::Service { + Metrics::new(inner, self.inner.clone(), self.transport_label) + } +} + +/// Metrics middleware. +#[derive(Clone)] +pub struct Metrics { + service: S, + metrics: RpcMetrics, + transport_label: &'static str, +} + +impl Metrics { + /// Create a new metrics middleware. + pub fn new(service: S, metrics: RpcMetrics, transport_label: &'static str) -> Metrics { + Metrics { service, metrics, transport_label } + } +} + +impl<'a, S> RpcServiceT<'a> for Metrics +where + S: Send + Sync + RpcServiceT<'a>, +{ + type Future = ResponseFuture<'a, S::Future>; + + fn call(&self, req: Request<'a>) -> Self::Future { + let now = Instant::now(); + + log::trace!( + target: "rpc_metrics", + "[{}] on_call name={} params={:?}", + self.transport_label, + req.method_name(), + req.params(), + ); + self.metrics + .calls_started + .with_label_values(&[self.transport_label, req.method_name()]) + .inc(); + + ResponseFuture { + fut: self.service.call(req.clone()), + metrics: self.metrics.clone(), + req, + now, + transport_label: self.transport_label, + } + } +} + +/// Response future for metrics. +#[pin_project] +pub struct ResponseFuture<'a, F> { + #[pin] + fut: F, + metrics: RpcMetrics, + req: Request<'a>, + now: Instant, + transport_label: &'static str, +} + +impl<'a, F> std::fmt::Debug for ResponseFuture<'a, F> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("ResponseFuture") + } +} + +impl<'a, F: Future> Future for ResponseFuture<'a, F> { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + let res = this.fut.poll(cx); + if let Poll::Ready(rp) = &res { + let method_name = this.req.method_name(); + let transport_label = &this.transport_label; + let now = this.now; + let metrics = &this.metrics; + + log::trace!(target: "rpc_metrics", "[{transport_label}] on_response started_at={:?}", now); + log::trace!(target: "rpc_metrics::extra", "[{transport_label}] result={:?}", rp); + + let micros = now.elapsed().as_micros(); + log::debug!( + target: "rpc_metrics", + "[{transport_label}] {method_name} call took {} μs", + micros, + ); + metrics + .calls_time + .with_label_values(&[transport_label, method_name]) + .observe(micros as _); + metrics + .calls_finished + .with_label_values(&[ + transport_label, + method_name, + // the label "is_error", so `success` should be regarded as false + // and vice-versa to be registrered correctly. + if rp.is_success() { "false" } else { "true" }, + ]) + .inc(); + } + res + } +} diff --git a/substrate/client/rpc-servers/src/middleware/mod.rs b/substrate/client/rpc-servers/src/middleware/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..cac516913d327f46ccf6ba1f81c7fb447873509b --- /dev/null +++ b/substrate/client/rpc-servers/src/middleware/mod.rs @@ -0,0 +1,27 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! JSON-RPC specific middleware. + +/// Grafana metrics middleware. +pub mod metrics; +/// Rate limit middleware. +pub mod rate_limit; + +pub use metrics::*; +pub use rate_limit::*; diff --git a/substrate/client/rpc-servers/src/middleware/rate_limit.rs b/substrate/client/rpc-servers/src/middleware/rate_limit.rs new file mode 100644 index 0000000000000000000000000000000000000000..cdcc3ebf66f7d0ab9d2ba515b0c128b55281f590 --- /dev/null +++ b/substrate/client/rpc-servers/src/middleware/rate_limit.rs @@ -0,0 +1,107 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! RPC rate limiting middleware. + +use std::{num::NonZeroU32, sync::Arc, time::Duration}; + +use futures::future::{BoxFuture, FutureExt}; +use governor::{ + clock::{Clock, DefaultClock, QuantaClock}, + middleware::NoOpMiddleware, + state::{InMemoryState, NotKeyed}, + Jitter, +}; +use jsonrpsee::{ + server::middleware::rpc::RpcServiceT, + types::{ErrorObject, Id, Request}, + MethodResponse, +}; + +type RateLimitInner = governor::RateLimiter; + +const MAX_JITTER: Duration = Duration::from_millis(50); +const MAX_RETRIES: usize = 10; + +/// JSON-RPC rate limit middleware layer. +#[derive(Debug, Clone)] +pub struct RateLimitLayer(governor::Quota); + +impl RateLimitLayer { + /// Create new rate limit enforced per minute. + pub fn per_minute(n: NonZeroU32) -> Self { + Self(governor::Quota::per_minute(n)) + } +} + +/// JSON-RPC rate limit middleware +pub struct RateLimit { + service: S, + rate_limit: Arc, + clock: QuantaClock, +} + +impl tower::Layer for RateLimitLayer { + type Service = RateLimit; + + fn layer(&self, service: S) -> Self::Service { + let clock = QuantaClock::default(); + RateLimit { + service, + rate_limit: Arc::new(RateLimitInner::direct_with_clock(self.0, &clock)), + clock, + } + } +} + +impl<'a, S> RpcServiceT<'a> for RateLimit +where + S: Send + Sync + RpcServiceT<'a> + Clone + 'static, +{ + type Future = BoxFuture<'a, MethodResponse>; + + fn call(&self, req: Request<'a>) -> Self::Future { + let service = self.service.clone(); + let rate_limit = self.rate_limit.clone(); + let clock = self.clock.clone(); + + async move { + let mut attempts = 0; + let jitter = Jitter::up_to(MAX_JITTER); + + loop { + if attempts >= MAX_RETRIES { + break reject_too_many_calls(req.id); + } + + if let Err(rejected) = rate_limit.check() { + tokio::time::sleep(jitter + rejected.wait_time_from(clock.now())).await; + } else { + break service.call(req).await; + } + + attempts += 1; + } + } + .boxed() + } +} + +fn reject_too_many_calls(id: Id) -> MethodResponse { + MethodResponse::error(id, ErrorObject::owned(-32999, "RPC rate limit exceeded", None::<()>)) +} diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 6d0e7e07848535d01cdc56214963105794a9c93f..c62b3e789d389047f66004f5f76a6bb947ce8b9e 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } # Internal chain structures for "chain_spec". sc-chain-spec = { path = "../chain-spec" } # Pool for submitting extrinsics required by "transaction" @@ -31,22 +31,24 @@ sc-client-api = { path = "../api" } sc-utils = { path = "../utils" } sc-rpc = { path = "../rpc" } codec = { package = "parity-scale-codec", version = "3.6.1" } -thiserror = "1.0" -serde = "1.0" +thiserror = { workspace = true } +serde = { workspace = true, default-features = true } hex = "0.4" futures = "0.3.21" parking_lot = "0.12.1" tokio-stream = { version = "0.1.14", features = ["sync"] } tokio = { version = "1.22.0", features = ["sync"] } array-bytes = "6.1" -log = "0.4.17" +log = { workspace = true, default-features = true } futures-util = { version = "0.3.30", default-features = false } +rand = "0.8.5" [dev-dependencies] -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } tokio = { version = "1.22.0", features = ["macros"] } substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } substrate-test-runtime = { path = "../../test-utils/runtime" } +substrate-test-runtime-transaction-pool = { path = "../../test-utils/runtime/transaction-pool" } sp-consensus = { path = "../../primitives/consensus/common" } sp-externalities = { path = "../../primitives/externalities" } sp-maybe-compressed-blob = { path = "../../primitives/maybe-compressed-blob" } @@ -54,3 +56,4 @@ sc-block-builder = { path = "../block-builder" } sc-service = { path = "../service", features = ["test-helpers"] } assert_matches = "1.3.0" pretty_assertions = "1.2.1" +sc-transaction-pool = { path = "../transaction-pool" } diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index 09b2410eac6e875b9fd19433bc67b49b6e5da947..1803ffa3a3183628a8eff81a10c05efdddc5e962 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -32,8 +32,7 @@ use super::{ use assert_matches::assert_matches; use codec::{Decode, Encode}; use jsonrpsee::{ - core::{EmptyServerParams as EmptyParams, Error}, - rpc_params, RpcModule, + core::EmptyServerParams as EmptyParams, rpc_params, MethodsError as Error, RpcModule, }; use sc_block_builder::BlockBuilderBuilder; use sc_client_api::ChildInfo; @@ -294,7 +293,7 @@ async fn archive_call() { ) .await .unwrap_err(); - assert_matches!(err, Error::Call(err) if err.code() == 3001 && err.message().contains("Invalid parameter")); + assert_matches!(err, Error::JsonRpc(err) if err.code() == 3001 && err.message().contains("Invalid parameter")); // Pass an invalid parameters that cannot be decode. let err = api @@ -305,7 +304,7 @@ async fn archive_call() { ) .await .unwrap_err(); - assert_matches!(err, Error::Call(err) if err.code() == 3001 && err.message().contains("Invalid parameter")); + assert_matches!(err, Error::JsonRpc(err) if err.code() == 3001 && err.message().contains("Invalid parameter")); // Invalid hash. let result: MethodResult = api diff --git a/substrate/client/rpc-spec-v2/src/chain_head/api.rs b/substrate/client/rpc-spec-v2/src/chain_head/api.rs index 7c3b8d81c82aefbf14344524140005ad8cd0b479..00000e1fb277bbc49aeb343d233b00d408fc45c4 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/api.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/api.rs @@ -26,7 +26,7 @@ use crate::{ }, common::events::StorageQuery, }; -use jsonrpsee::proc_macros::rpc; +use jsonrpsee::{proc_macros::rpc, server::ResponsePayload}; use sp_rpc::list::ListOrValue; #[rpc(client, server)] @@ -59,7 +59,7 @@ pub trait ChainHeadApi { &self, follow_subscription: String, hash: Hash, - ) -> Result; + ) -> ResponsePayload<'static, MethodResponse>; /// Retrieves the header of a pinned block. /// @@ -92,7 +92,7 @@ pub trait ChainHeadApi { hash: Hash, items: Vec>, child_trie: Option, - ) -> Result; + ) -> ResponsePayload<'static, MethodResponse>; /// Call into the Runtime API at a specified block's state. /// @@ -106,7 +106,7 @@ pub trait ChainHeadApi { hash: Hash, function: String, call_parameters: String, - ) -> Result; + ) -> ResponsePayload<'static, MethodResponse>; /// Unpin a block or multiple blocks reported by the `follow` method. /// diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs index 0e207addcaeb71d4bedd9de2c2ba29d4f192640f..2bda22b452391e5b3ef3f7367b43331079591aa3 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -36,7 +36,8 @@ use crate::{ use codec::Encode; use futures::future::FutureExt; use jsonrpsee::{ - core::async_trait, types::SubscriptionId, PendingSubscriptionSink, SubscriptionSink, + core::async_trait, server::ResponsePayload, types::SubscriptionId, MethodResponseFuture, + PendingSubscriptionSink, SubscriptionSink, }; use log::debug; use sc_client_api::{ @@ -218,16 +219,17 @@ where &self, follow_subscription: String, hash: Block::Hash, - ) -> Result { + ) -> ResponsePayload<'static, MethodResponse> { let mut block_guard = match self.subscriptions.lock_block(&follow_subscription, hash, 1) { Ok(block) => block, Err(SubscriptionManagementError::SubscriptionAbsent) | - Err(SubscriptionManagementError::ExceededLimits) => return Ok(MethodResponse::LimitReached), + Err(SubscriptionManagementError::ExceededLimits) => + return ResponsePayload::success(MethodResponse::LimitReached), Err(SubscriptionManagementError::BlockHashAbsent) => { // Block is not part of the subscription. - return Err(ChainHeadRpcError::InvalidBlock.into()) + return ResponsePayload::error(ChainHeadRpcError::InvalidBlock); }, - Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()), + Err(_) => return ResponsePayload::error(ChainHeadRpcError::InvalidBlock), }; let operation_id = block_guard.operation().operation_id(); @@ -254,7 +256,7 @@ where hash ); self.subscriptions.remove_subscription(&follow_subscription); - return Err(ChainHeadRpcError::InvalidBlock.into()) + return ResponsePayload::error(ChainHeadRpcError::InvalidBlock) }, Err(error) => FollowEvent::::OperationError(OperationError { operation_id: operation_id.clone(), @@ -262,8 +264,20 @@ where }), }; - let _ = block_guard.response_sender().unbounded_send(event); - Ok(MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items: None })) + let (rp, rp_fut) = method_started_response(operation_id, None); + + let fut = async move { + // Events should only by generated + // if the response was successfully propagated. + if rp_fut.await.is_err() { + return; + } + let _ = block_guard.response_sender().unbounded_send(event); + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + + rp } fn chain_head_unstable_header( @@ -294,31 +308,40 @@ where hash: Block::Hash, items: Vec>, child_trie: Option, - ) -> Result { + ) -> ResponsePayload<'static, MethodResponse> { // Gain control over parameter parsing and returned error. - let items = items + let items = match items .into_iter() .map(|query| { let key = StorageKey(parse_hex_param(query.key)?); Ok(StorageQuery { key, query_type: query.query_type }) }) - .collect::, ChainHeadRpcError>>()?; + .collect::, ChainHeadRpcError>>() + { + Ok(items) => items, + Err(err) => { + return ResponsePayload::error(err); + }, + }; - let child_trie = child_trie - .map(|child_trie| parse_hex_param(child_trie)) - .transpose()? - .map(ChildInfo::new_default_from_vec); + let child_trie = match child_trie.map(|child_trie| parse_hex_param(child_trie)).transpose() + { + Ok(c) => c.map(ChildInfo::new_default_from_vec), + Err(e) => return ResponsePayload::error(e), + }; let mut block_guard = match self.subscriptions.lock_block(&follow_subscription, hash, items.len()) { Ok(block) => block, Err(SubscriptionManagementError::SubscriptionAbsent) | - Err(SubscriptionManagementError::ExceededLimits) => return Ok(MethodResponse::LimitReached), + Err(SubscriptionManagementError::ExceededLimits) => { + return ResponsePayload::success(MethodResponse::LimitReached); + }, Err(SubscriptionManagementError::BlockHashAbsent) => { // Block is not part of the subscription. - return Err(ChainHeadRpcError::InvalidBlock.into()) + return ResponsePayload::error(ChainHeadRpcError::InvalidBlock) }, - Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()), + Err(_) => return ResponsePayload::error(ChainHeadRpcError::InvalidBlock), }; let mut storage_client = ChainHeadStorage::::new( @@ -334,16 +357,21 @@ where let mut items = items; items.truncate(num_operations); + let (rp, rp_is_success) = method_started_response(operation_id, Some(discarded)); + let fut = async move { + // Events should only by generated + // if the response was successfully propagated. + if rp_is_success.await.is_err() { + return; + } storage_client.generate_events(block_guard, hash, items, child_trie).await; }; self.executor .spawn_blocking("substrate-rpc-subscription", Some("rpc"), fut.boxed()); - Ok(MethodResponse::Started(MethodResponseStarted { - operation_id, - discarded_items: Some(discarded), - })) + + rp } fn chain_head_unstable_call( @@ -352,29 +380,31 @@ where hash: Block::Hash, function: String, call_parameters: String, - ) -> Result { - let call_parameters = Bytes::from(parse_hex_param(call_parameters)?); + ) -> ResponsePayload<'static, MethodResponse> { + let call_parameters = match parse_hex_param(call_parameters) { + Ok(hex) => Bytes::from(hex), + Err(err) => return ResponsePayload::error(err), + }; let mut block_guard = match self.subscriptions.lock_block(&follow_subscription, hash, 1) { Ok(block) => block, Err(SubscriptionManagementError::SubscriptionAbsent) | Err(SubscriptionManagementError::ExceededLimits) => { // Invalid invalid subscription ID. - return Ok(MethodResponse::LimitReached) + return ResponsePayload::success(MethodResponse::LimitReached) }, Err(SubscriptionManagementError::BlockHashAbsent) => { // Block is not part of the subscription. - return Err(ChainHeadRpcError::InvalidBlock.into()) + return ResponsePayload::error(ChainHeadRpcError::InvalidBlock) }, - Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()), + Err(_) => return ResponsePayload::error(ChainHeadRpcError::InvalidBlock), }; // Reject subscription if with_runtime is false. if !block_guard.has_runtime() { - return Err(ChainHeadRpcError::InvalidRuntimeCall( + return ResponsePayload::error(ChainHeadRpcError::InvalidRuntimeCall( "The runtime updates flag must be set".to_string(), - ) - .into()) + )); } let operation_id = block_guard.operation().operation_id(); @@ -395,11 +425,20 @@ where }) }); - let _ = block_guard.response_sender().unbounded_send(event); - Ok(MethodResponse::Started(MethodResponseStarted { - operation_id: operation_id.clone(), - discarded_items: None, - })) + let (rp, rp_fut) = method_started_response(operation_id, None); + + let fut = async move { + // Events should only by generated + // if the response was successfully propagated. + if rp_fut.await.is_err() { + return; + } + let _ = block_guard.response_sender().unbounded_send(event); + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + + rp } fn chain_head_unstable_unpin( @@ -424,6 +463,8 @@ where // Block is not part of the subscription. Err(ChainHeadRpcError::InvalidBlock) }, + Err(SubscriptionManagementError::DuplicateHashes) => + Err(ChainHeadRpcError::InvalidDuplicateHashes), Err(_) => Err(ChainHeadRpcError::InvalidBlock), } } @@ -461,3 +502,11 @@ where Ok(()) } } + +fn method_started_response( + operation_id: String, + discarded_items: Option, +) -> (ResponsePayload<'static, MethodResponse>, MethodResponseFuture) { + let rp = MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items }); + ResponsePayload::success(rp).notify_on_completion() +} diff --git a/substrate/client/rpc-spec-v2/src/chain_head/error.rs b/substrate/client/rpc-spec-v2/src/chain_head/error.rs index bf290edb29eefd4a3dbe2316973ada8447cdd933..8c50e445aa0cf6480d3a672f7120cd7a492f4c99 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/error.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/error.rs @@ -32,6 +32,9 @@ pub enum Error { /// Wait-for-continue event not generated. #[error("Wait for continue event was not generated for the subscription")] InvalidContinue, + /// Received duplicate hashes for the `chainHead_unpin` method. + #[error("Received duplicate hashes for the `chainHead_unpin` method")] + InvalidDuplicateHashes, /// Invalid parameter provided to the RPC method. #[error("Invalid parameter: {0}")] InvalidParam(String), @@ -49,6 +52,8 @@ pub mod rpc_spec_v2 { pub const INVALID_RUNTIME_CALL: i32 = -32802; /// Wait-for-continue event not generated. pub const INVALID_CONTINUE: i32 = -32803; + /// Received duplicate hashes for the `chainHead_unpin` method. + pub const INVALID_DUPLICATE_HASHES: i32 = -32804; } /// General purpose errors, as defined in @@ -71,6 +76,8 @@ impl From for ErrorObject<'static> { ErrorObject::owned(rpc_spec_v2::INVALID_RUNTIME_CALL, msg, None::<()>), Error::InvalidContinue => ErrorObject::owned(rpc_spec_v2::INVALID_CONTINUE, msg, None::<()>), + Error::InvalidDuplicateHashes => + ErrorObject::owned(rpc_spec_v2::INVALID_DUPLICATE_HASHES, msg, None::<()>), Error::InvalidParam(_) => ErrorObject::owned(json_rpc_spec::INVALID_PARAM_ERROR, msg, None::<()>), Error::InternalError(_) => diff --git a/substrate/client/rpc-spec-v2/src/chain_head/mod.rs b/substrate/client/rpc-spec-v2/src/chain_head/mod.rs index 4cbbd00f64f31f04f44ff83f9e5980e7d22e7308..c9fe19aca2b1898da25e45019e1924256d732d9a 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/mod.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/mod.rs @@ -23,7 +23,7 @@ //! Methods are prefixed by `chainHead`. #[cfg(test)] -mod test_utils; +pub mod test_utils; #[cfg(test)] mod tests; diff --git a/substrate/client/rpc-spec-v2/src/chain_head/subscription/error.rs b/substrate/client/rpc-spec-v2/src/chain_head/subscription/error.rs index 38e8fd7384fcbd222bbfc385de602a834613c07d..2c22e51ca4dc70102fca98f4eea7f66d8e044a02 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/subscription/error.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/subscription/error.rs @@ -38,6 +38,9 @@ pub enum SubscriptionManagementError { /// The specified subscription ID is not present. #[error("Subscription is absent")] SubscriptionAbsent, + /// The unpin method was called with duplicate hashes. + #[error("Duplicate hashes")] + DuplicateHashes, /// Custom error. #[error("Subscription error {0}")] Custom(String), @@ -52,7 +55,8 @@ impl PartialEq for SubscriptionManagementError { (Self::Blockchain(_), Self::Blockchain(_)) | (Self::BlockHashAbsent, Self::BlockHashAbsent) | (Self::BlockHeaderAbsent, Self::BlockHeaderAbsent) | - (Self::SubscriptionAbsent, Self::SubscriptionAbsent) => true, + (Self::SubscriptionAbsent, Self::SubscriptionAbsent) | + (Self::DuplicateHashes, Self::DuplicateHashes) => true, (Self::Custom(lhs), Self::Custom(rhs)) => lhs == rhs, _ => false, } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs index 2b250f3dc2cf2d0b52a222256b314eefc035a310..d2879679501fd17eada1b826ff01478cce24b7a3 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs @@ -22,7 +22,7 @@ use sc_client_api::Backend; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_runtime::traits::Block as BlockT; use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{hash_map::Entry, HashMap, HashSet}, sync::{atomic::AtomicBool, Arc}, time::{Duration, Instant}, }; @@ -750,11 +750,27 @@ impl> SubscriptionsInner { } } + /// Ensure the provided hashes are unique. + fn ensure_hash_uniqueness( + hashes: impl IntoIterator + Clone, + ) -> Result<(), SubscriptionManagementError> { + let mut set = HashSet::new(); + hashes.into_iter().try_for_each(|hash| { + if !set.insert(hash) { + Err(SubscriptionManagementError::DuplicateHashes) + } else { + Ok(()) + } + }) + } + pub fn unpin_blocks( &mut self, sub_id: &str, hashes: impl IntoIterator + Clone, ) -> Result<(), SubscriptionManagementError> { + Self::ensure_hash_uniqueness(hashes.clone())?; + let Some(sub) = self.subs.get_mut(sub_id) else { return Err(SubscriptionManagementError::SubscriptionAbsent) }; @@ -985,6 +1001,76 @@ mod tests { assert!(block_state.is_none()); } + #[test] + fn unpin_duplicate_hashes() { + let (backend, mut client) = init_backend(); + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap() + .build() + .unwrap() + .block; + let hash_1 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(hash_1) + .with_parent_block_number(1) + .build() + .unwrap() + .build() + .unwrap() + .block; + let hash_2 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(hash_2) + .with_parent_block_number(2) + .build() + .unwrap() + .build() + .unwrap() + .block; + let hash_3 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + + let mut subs = + SubscriptionsInner::new(10, Duration::from_secs(10), MAX_OPERATIONS_PER_SUB, backend); + let id_1 = "abc".to_string(); + let id_2 = "abcd".to_string(); + + // Pin all blocks for the first subscription. + let _stop = subs.insert_subscription(id_1.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_1, hash_1).unwrap(), true); + assert_eq!(subs.pin_block(&id_1, hash_2).unwrap(), true); + assert_eq!(subs.pin_block(&id_1, hash_3).unwrap(), true); + + // Pin only block 2 for the second subscription. + let _stop = subs.insert_subscription(id_2.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_2, hash_2).unwrap(), true); + + // Check reference count. + assert_eq!(*subs.global_blocks.get(&hash_1).unwrap(), 1); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 2); + assert_eq!(*subs.global_blocks.get(&hash_3).unwrap(), 1); + + // Unpin the same block twice. + let err = subs.unpin_blocks(&id_1, vec![hash_1, hash_1, hash_2, hash_2]).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::DuplicateHashes); + + // Check reference count must be unaltered. + assert_eq!(*subs.global_blocks.get(&hash_1).unwrap(), 1); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 2); + assert_eq!(*subs.global_blocks.get(&hash_3).unwrap(), 1); + + // Unpin the blocks correctly. + subs.unpin_blocks(&id_1, vec![hash_1, hash_2]).unwrap(); + assert_eq!(subs.global_blocks.get(&hash_1), None); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 1); + assert_eq!(*subs.global_blocks.get(&hash_3).unwrap(), 1); + } + #[test] fn subscription_lock_block() { let builder = TestClientBuilder::new(); diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index 955a361e3eadb455891a68e8ec7e74e4a95c84e0..9544736d84c8e634613d0a7e363e9398f1aa0b9d 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -27,8 +27,7 @@ use assert_matches::assert_matches; use codec::{Decode, Encode}; use futures::Future; use jsonrpsee::{ - core::{error::Error, server::Subscription as RpcSubscription}, - rpc_params, RpcModule, + core::server::Subscription as RpcSubscription, rpc_params, MethodsError as Error, RpcModule, }; use sc_block_builder::BlockBuilderBuilder; use sc_client_api::ChildInfo; @@ -359,7 +358,7 @@ async fn get_header() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Obtain the valid header. @@ -388,7 +387,7 @@ async fn get_body() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Valid call. @@ -473,7 +472,7 @@ async fn call_runtime() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Pass an invalid parameters that cannot be decode. @@ -486,7 +485,7 @@ async fn call_runtime() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message().contains("Invalid parameter") + Error::JsonRpc(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message().contains("Invalid parameter") ); // Valid call. @@ -589,7 +588,7 @@ async fn call_runtime_without_flag() { .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_RUNTIME_CALL && err.message().contains("subscription was started with `withRuntime` set to `false`") + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_RUNTIME_CALL && err.message().contains("subscription was started with `withRuntime` set to `false`") ); } @@ -627,7 +626,7 @@ async fn get_storage_hash() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Valid call without storage at the key. @@ -895,7 +894,7 @@ async fn get_storage_value() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Valid call without storage at the key. @@ -1571,7 +1570,7 @@ async fn follow_with_unpin() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // To not exceed the number of pinned blocks, we need to unpin before the next import. @@ -1617,6 +1616,108 @@ async fn follow_with_unpin() { assert!(sub.next::>().await.is_none()); } +#[tokio::test] +async fn unpin_duplicate_hashes() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + ChainHeadConfig { + global_max_pinned_blocks: 3, + subscription_max_pinned_duration: Duration::from_secs(MAX_PINNED_SECS), + subscription_max_ongoing_operations: MAX_OPERATIONS, + operation_max_storage_items: MAX_PAGINATION_LIMIT, + }, + ) + .into_rpc(); + + let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap(); + let sub_id = sub.subscription_id(); + let sub_id = serde_json::to_string(&sub_id).unwrap(); + + let block = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap() + .build() + .unwrap() + .block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Ensure the imported block is propagated and pinned for this subscription. + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::Initialized(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + // Try to unpin duplicate hashes. + let err = api + .call::<_, serde_json::Value>( + "chainHead_unstable_unpin", + rpc_params![&sub_id, vec![&block_hash, &block_hash]], + ) + .await + .unwrap_err(); + assert_matches!(err, + Error::JsonRpc(err) if err.code() == super::error::rpc_spec_v2::INVALID_DUPLICATE_HASHES && err.message() == "Received duplicate hashes for the `chainHead_unpin` method" + ); + + // Block tree: + // finalized_block -> block -> block2 + let block2 = BlockBuilderBuilder::new(&*client) + .on_parent_block(block.hash()) + .with_parent_block_number(1) + .build() + .unwrap() + .build() + .unwrap() + .block; + let block_hash_2 = format!("{:?}", block2.header.hash()); + client.import(BlockOrigin::Own, block2.clone()).await.unwrap(); + + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + // Try to unpin duplicate hashes. + let err = api + .call::<_, serde_json::Value>( + "chainHead_unstable_unpin", + rpc_params![&sub_id, vec![&block_hash, &block_hash_2, &block_hash]], + ) + .await + .unwrap_err(); + assert_matches!(err, + Error::JsonRpc(err) if err.code() == super::error::rpc_spec_v2::INVALID_DUPLICATE_HASHES && err.message() == "Received duplicate hashes for the `chainHead_unpin` method" + ); + + // Can unpin blocks. + let _res: () = api + .call("chainHead_unstable_unpin", rpc_params![&sub_id, vec![&block_hash, &block_hash_2]]) + .await + .unwrap(); +} + #[tokio::test] async fn follow_with_multiple_unpin_hashes() { let builder = TestClientBuilder::new(); @@ -1720,7 +1821,7 @@ async fn follow_with_multiple_unpin_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); let _res: () = api @@ -1737,7 +1838,7 @@ async fn follow_with_multiple_unpin_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); // Unpin multiple blocks. @@ -1755,7 +1856,7 @@ async fn follow_with_multiple_unpin_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); let err = api @@ -1766,7 +1867,7 @@ async fn follow_with_multiple_unpin_hashes() { .await .unwrap_err(); assert_matches!(err, - Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" + Error::JsonRpc(ref err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash" ); } diff --git a/substrate/client/rpc-spec-v2/src/transaction/api.rs b/substrate/client/rpc-spec-v2/src/transaction/api.rs index 53c83b662a35fdcb67a5e212307c6b5ec7c2a61f..33af9c9533388a1e4d5832a390c8eb96da756905 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/api.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/api.rs @@ -18,8 +18,8 @@ //! API trait for transactions. -use crate::transaction::event::TransactionEvent; -use jsonrpsee::proc_macros::rpc; +use crate::transaction::{error::ErrorBroadcast, event::TransactionEvent}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sp_core::Bytes; #[rpc(client, server)] @@ -28,6 +28,10 @@ pub trait TransactionApi { /// /// See [`TransactionEvent`](crate::transaction::event::TransactionEvent) for details on /// transaction life cycle. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. #[subscription( name = "transactionWatch_unstable_submitAndWatch" => "transactionWatch_unstable_watchEvent", unsubscribe = "transactionWatch_unstable_unwatch", @@ -35,3 +39,22 @@ pub trait TransactionApi { )] fn submit_and_watch(&self, bytes: Bytes); } + +#[rpc(client, server)] +pub trait TransactionBroadcastApi { + /// Broadcast an extrinsic to the chain. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "transaction_unstable_broadcast")] + fn broadcast(&self, bytes: Bytes) -> RpcResult>; + + /// Broadcast an extrinsic to the chain. + /// + /// # Unstable + /// + /// This method is unstable and subject to change in the future. + #[method(name = "transaction_unstable_stop")] + fn stop_broadcast(&self, operation_id: String) -> Result<(), ErrorBroadcast>; +} diff --git a/substrate/client/rpc-spec-v2/src/transaction/error.rs b/substrate/client/rpc-spec-v2/src/transaction/error.rs index d2de07afd5955cd2d41148bab0119384b117bf42..116977af66001096ff7cb14775b768451833a866 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/error.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/error.rs @@ -21,6 +21,7 @@ //! Errors are interpreted as transaction events for subscriptions. use crate::transaction::event::{TransactionError, TransactionEvent}; +use jsonrpsee::types::error::ErrorObject; use sc_transaction_pool_api::error::Error as PoolError; use sp_runtime::transaction_validity::InvalidTransaction; @@ -98,3 +99,29 @@ impl From for TransactionEvent { } } } + +/// TransactionBroadcast error. +#[derive(Debug, thiserror::Error)] +pub enum ErrorBroadcast { + /// The provided operation ID is invalid. + #[error("Invalid operation id")] + InvalidOperationID, +} + +/// General purpose errors, as defined in +/// . +pub mod json_rpc_spec { + /// Invalid parameter error. + pub const INVALID_PARAM_ERROR: i32 = -32602; +} + +impl From for ErrorObject<'static> { + fn from(e: ErrorBroadcast) -> Self { + let msg = e.to_string(); + + match e { + ErrorBroadcast::InvalidOperationID => + ErrorObject::owned(json_rpc_spec::INVALID_PARAM_ERROR, msg, None::<()>), + } + } +} diff --git a/substrate/client/rpc-spec-v2/src/transaction/event.rs b/substrate/client/rpc-spec-v2/src/transaction/event.rs index 8b80fcda17beb22cece35c928f10d72175fa9458..882ac8490b07c00432c4296964b7f995329df3dc 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/event.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/event.rs @@ -20,23 +20,6 @@ use serde::{Deserialize, Serialize}; -/// The transaction was broadcasted to a number of peers. -/// -/// # Note -/// -/// The RPC does not guarantee that the peers have received the -/// transaction. -/// -/// When the number of peers is zero, the event guarantees that -/// shutting down the local node will lead to the transaction -/// not being included in the chain. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TransactionBroadcasted { - /// The number of peers the transaction was broadcasted to. - pub num_peers: usize, -} - /// The transaction was included in a block of the chain. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -59,9 +42,6 @@ pub struct TransactionError { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TransactionDropped { - /// True if the transaction was broadcasted to other peers and - /// may still be included in the block. - pub broadcasted: bool, /// Reason of the event. pub error: String, } @@ -70,20 +50,17 @@ pub struct TransactionDropped { /// /// The status events can be grouped based on their kinds as: /// -/// 1. Runtime validated the transaction: +/// 1. Runtime validated the transaction and it entered the pool: /// - `Validated` /// -/// 2. Inside the `Ready` queue: -/// - `Broadcast` -/// -/// 3. Leaving the pool: +/// 2. Leaving the pool: /// - `BestChainBlockIncluded` /// - `Invalid` /// -/// 4. Block finalized: +/// 3. Block finalized: /// - `Finalized` /// -/// 5. At any time: +/// 4. At any time: /// - `Dropped` /// - `Error` /// @@ -101,8 +78,6 @@ pub struct TransactionDropped { pub enum TransactionEvent { /// The transaction was validated by the runtime. Validated, - /// The transaction was broadcasted to a number of peers. - Broadcasted(TransactionBroadcasted), /// The transaction was included in a best block of the chain. /// /// # Note @@ -159,7 +134,6 @@ enum TransactionEventBlockIR { #[serde(tag = "event")] enum TransactionEventNonBlockIR { Validated, - Broadcasted(TransactionBroadcasted), Error(TransactionError), Invalid(TransactionError), Dropped(TransactionDropped), @@ -186,8 +160,6 @@ impl From> for TransactionEventIR { match value { TransactionEvent::Validated => TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Validated), - TransactionEvent::Broadcasted(event) => - TransactionEventIR::NonBlock(TransactionEventNonBlockIR::Broadcasted(event)), TransactionEvent::BestChainBlockIncluded(event) => TransactionEventIR::Block(TransactionEventBlockIR::BestChainBlockIncluded(event)), TransactionEvent::Finalized(event) => @@ -207,8 +179,6 @@ impl From> for TransactionEvent { match value { TransactionEventIR::NonBlock(status) => match status { TransactionEventNonBlockIR::Validated => TransactionEvent::Validated, - TransactionEventNonBlockIR::Broadcasted(event) => - TransactionEvent::Broadcasted(event), TransactionEventNonBlockIR::Error(event) => TransactionEvent::Error(event), TransactionEventNonBlockIR::Invalid(event) => TransactionEvent::Invalid(event), TransactionEventNonBlockIR::Dropped(event) => TransactionEvent::Dropped(event), @@ -239,19 +209,6 @@ mod tests { assert_eq!(event_dec, event); } - #[test] - fn broadcasted_event() { - let event: TransactionEvent<()> = - TransactionEvent::Broadcasted(TransactionBroadcasted { num_peers: 2 }); - let ser = serde_json::to_string(&event).unwrap(); - - let exp = r#"{"event":"broadcasted","numPeers":2}"#; - assert_eq!(ser, exp); - - let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); - assert_eq!(event_dec, event); - } - #[test] fn best_chain_event() { let event: TransactionEvent<()> = TransactionEvent::BestChainBlockIncluded(None); @@ -320,13 +277,11 @@ mod tests { #[test] fn dropped_event() { - let event: TransactionEvent<()> = TransactionEvent::Dropped(TransactionDropped { - broadcasted: true, - error: "abc".to_string(), - }); + let event: TransactionEvent<()> = + TransactionEvent::Dropped(TransactionDropped { error: "abc".to_string() }); let ser = serde_json::to_string(&event).unwrap(); - let exp = r#"{"event":"dropped","broadcasted":true,"error":"abc"}"#; + let exp = r#"{"event":"dropped","error":"abc"}"#; assert_eq!(ser, exp); let event_dec: TransactionEvent<()> = serde_json::from_str(exp).unwrap(); diff --git a/substrate/client/rpc-spec-v2/src/transaction/mod.rs b/substrate/client/rpc-spec-v2/src/transaction/mod.rs index 212912ba1c728feb5e01614a74fcab09ce5bc079..514ccf047dc28570c78543755577661a6a7791af 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/mod.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/mod.rs @@ -25,14 +25,16 @@ //! //! Methods are prefixed by `transaction`. +#[cfg(test)] +mod tests; + pub mod api; pub mod error; pub mod event; pub mod transaction; +pub mod transaction_broadcast; -pub use api::TransactionApiServer; -pub use event::{ - TransactionBlock, TransactionBroadcasted, TransactionDropped, TransactionError, - TransactionEvent, -}; +pub use api::{TransactionApiServer, TransactionBroadcastApiServer}; +pub use event::{TransactionBlock, TransactionDropped, TransactionError, TransactionEvent}; pub use transaction::Transaction; +pub use transaction_broadcast::TransactionBroadcast; diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests.rs b/substrate/client/rpc-spec-v2/src/transaction/tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..382f5adeae19ebd7da366036b5092b0208b62568 --- /dev/null +++ b/substrate/client/rpc-spec-v2/src/transaction/tests.rs @@ -0,0 +1,238 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use crate::{ + chain_head::test_utils::ChainHeadMockClient, hex_string, + transaction::TransactionBroadcast as RpcTransactionBroadcast, +}; +use assert_matches::assert_matches; +use codec::Encode; +use futures::Future; +use jsonrpsee::{rpc_params, MethodsError as Error, RpcModule}; +use sc_transaction_pool::*; +use sc_transaction_pool_api::{ChainEvent, MaintainedTransactionPool, TransactionPool}; +use sp_core::{testing::TaskExecutor, traits::SpawnNamed}; +use std::{pin::Pin, sync::Arc, time::Duration}; +use substrate_test_runtime_client::{prelude::*, AccountKeyring::*, Client}; +use substrate_test_runtime_transaction_pool::{uxt, TestApi}; +use tokio::sync::mpsc; + +type Block = substrate_test_runtime_client::runtime::Block; + +/// Wrap the `TaskExecutor` to know when the broadcast future is dropped. +#[derive(Clone)] +struct TaskExecutorBroadcast { + executor: TaskExecutor, + sender: mpsc::UnboundedSender<()>, +} + +/// The channel that receives events when the broadcast futures are dropped. +type TaskExecutorRecv = mpsc::UnboundedReceiver<()>; + +impl TaskExecutorBroadcast { + /// Construct a new `TaskExecutorBroadcast` and a receiver to know when the broadcast futures + /// are dropped. + fn new() -> (Self, TaskExecutorRecv) { + let (sender, recv) = mpsc::unbounded_channel(); + + (Self { executor: TaskExecutor::new(), sender }, recv) + } +} + +impl SpawnNamed for TaskExecutorBroadcast { + fn spawn( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { + let sender = self.sender.clone(); + let future = Box::pin(async move { + future.await; + let _ = sender.send(()); + }); + + self.executor.spawn(name, group, future) + } + + fn spawn_blocking( + &self, + name: &'static str, + group: Option<&'static str>, + future: futures::future::BoxFuture<'static, ()>, + ) { + let sender = self.sender.clone(); + let future = Box::pin(async move { + future.await; + let _ = sender.send(()); + }); + + self.executor.spawn_blocking(name, group, future) + } +} + +/// Initial Alice account nonce. +const ALICE_NONCE: u64 = 209; + +fn create_basic_pool_with_genesis( + test_api: Arc, +) -> (BasicPool, Pin + Send>>) { + let genesis_hash = { + test_api + .chain() + .read() + .block_by_number + .get(&0) + .map(|blocks| blocks[0].0.header.hash()) + .expect("there is block 0. qed") + }; + BasicPool::new_test(test_api, genesis_hash, genesis_hash) +} + +fn maintained_pool() -> (BasicPool, Arc, futures::executor::ThreadPool) { + let api = Arc::new(TestApi::with_alice_nonce(ALICE_NONCE)); + let (pool, background_task) = create_basic_pool_with_genesis(api.clone()); + + let thread_pool = futures::executor::ThreadPool::new().unwrap(); + thread_pool.spawn_ok(background_task); + (pool, api, thread_pool) +} + +fn setup_api() -> ( + Arc, + Arc>, + Arc>>, + RpcModule< + TransactionBroadcast, ChainHeadMockClient>>, + >, + TaskExecutorRecv, +) { + let (pool, api, _) = maintained_pool(); + let pool = Arc::new(pool); + + let builder = TestClientBuilder::new(); + let client = Arc::new(builder.build()); + let client_mock = Arc::new(ChainHeadMockClient::new(client.clone())); + + let (task_executor, executor_recv) = TaskExecutorBroadcast::new(); + + let tx_api = + RpcTransactionBroadcast::new(client_mock.clone(), pool.clone(), Arc::new(task_executor)) + .into_rpc(); + + (api, pool, client_mock, tx_api, executor_recv) +} + +#[tokio::test] +async fn tx_broadcast_enters_pool() { + let (api, pool, client_mock, tx_api, _) = setup_api(); + + // Start at block 1. + let block_1_header = api.push_block(1, vec![], true); + + let uxt = uxt(Alice, ALICE_NONCE); + let xt = hex_string(&uxt.encode()); + + let operation_id: String = + tx_api.call("transaction_unstable_broadcast", rpc_params![&xt]).await.unwrap(); + + // Announce block 1 to `transaction_unstable_broadcast`. + client_mock.trigger_import_stream(block_1_header).await; + + // Ensure the tx propagated from `transaction_unstable_broadcast` to the transaction pool. + + // TODO: Improve testability by extending the `transaction_unstable_broadcast` with + // a middleware trait that intercepts the transaction status for testing. + let mut num_retries = 12; + while num_retries > 0 && pool.status().ready != 1 { + tokio::time::sleep(Duration::from_secs(5)).await; + num_retries -= 1; + } + assert_eq!(1, pool.status().ready); + assert_eq!(uxt.encode().len(), pool.status().ready_bytes); + + // Import block 2 with the transaction included. + let block_2_header = api.push_block(2, vec![uxt.clone()], true); + let block_2 = block_2_header.hash(); + + // Announce block 2 to the pool. + let event = ChainEvent::NewBestBlock { hash: block_2, tree_route: None }; + pool.maintain(event).await; + + assert_eq!(0, pool.status().ready); + + // Stop call can still be made. + let _: () = tx_api + .call("transaction_unstable_stop", rpc_params![&operation_id]) + .await + .unwrap(); +} + +#[tokio::test] +async fn tx_broadcast_invalid_tx() { + let (_, pool, _, tx_api, mut exec_recv) = setup_api(); + + // Invalid parameters. + let err = tx_api + .call::<_, serde_json::Value>("transaction_unstable_broadcast", [1u8]) + .await + .unwrap_err(); + assert_matches!(err, + Error::JsonRpc(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid params" + ); + + assert_eq!(0, pool.status().ready); + + // Invalid transaction that cannot be decoded. The broadcast silently exits. + let xt = "0xdeadbeef"; + let operation_id: String = + tx_api.call("transaction_unstable_broadcast", rpc_params![&xt]).await.unwrap(); + + assert_eq!(0, pool.status().ready); + + // Await the broadcast future to exit. + // Without this we'd be subject to races, where we try to call the stop before the tx is + // dropped. + exec_recv.recv().await.unwrap(); + + // The broadcast future was dropped, and the operation is no longer active. + // When the operation is not active, either from the tx being finalized or a + // terminal error; the stop method should return an error. + let err = tx_api + .call::<_, serde_json::Value>("transaction_unstable_stop", rpc_params![&operation_id]) + .await + .unwrap_err(); + assert_matches!(err, + Error::JsonRpc(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid operation id" + ); +} + +#[tokio::test] +async fn tx_invalid_stop() { + let (_, _, _, tx_api, _) = setup_api(); + + // Make an invalid stop call. + let err = tx_api + .call::<_, serde_json::Value>("transaction_unstable_stop", ["invalid_operation_id"]) + .await + .unwrap_err(); + assert_matches!(err, + Error::JsonRpc(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid operation id" + ); +} diff --git a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs index b2cfa36c9c99f7bd3db5072e8adaabd1c6fed962..d44006392dca4a05a6c9f5bbf872c75bc7d5ee52 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs @@ -22,28 +22,22 @@ use crate::{ transaction::{ api::TransactionApiServer, error::Error, - event::{ - TransactionBlock, TransactionBroadcasted, TransactionDropped, TransactionError, - TransactionEvent, - }, + event::{TransactionBlock, TransactionDropped, TransactionError, TransactionEvent}, }, SubscriptionTaskExecutor, }; +use codec::Decode; +use futures::{StreamExt, TryFutureExt}; use jsonrpsee::{core::async_trait, types::error::ErrorObject, PendingSubscriptionSink}; +use sc_rpc::utils::pipe_from_stream; use sc_transaction_pool_api::{ error::IntoPoolError, BlockHash, TransactionFor, TransactionPool, TransactionSource, TransactionStatus, }; -use std::sync::Arc; - -use sc_rpc::utils::pipe_from_stream; -use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::Bytes; use sp_runtime::traits::Block as BlockT; - -use codec::Decode; -use futures::{StreamExt, TryFutureExt}; +use std::sync::Arc; /// An API for transaction RPC calls. pub struct Transaction { @@ -82,7 +76,7 @@ where Pool: TransactionPool + Sync + Send + 'static, Pool::Hash: Unpin, ::Hash: Unpin, - Client: HeaderBackend + ProvideRuntimeApi + Send + Sync + 'static, + Client: HeaderBackend + Send + Sync + 'static, { fn submit_and_watch(&self, pending: PendingSubscriptionSink, xt: Bytes) { let client = self.client.clone(); @@ -116,9 +110,7 @@ where match submit.await { Ok(stream) => { - let mut state = TransactionState::new(); - let stream = - stream.filter_map(move |event| async move { state.handle_event(event) }); + let stream = stream.filter_map(move |event| async move { handle_event(event) }); pipe_from_stream(pending, stream.boxed()).await; }, Err(err) => { @@ -134,66 +126,34 @@ where } } -/// The transaction's state that needs to be preserved between -/// multiple events generated by the transaction-pool. -/// -/// # Note -/// -/// In the future, the RPC server can submit only the last event when multiple -/// identical events happen in a row. -#[derive(Clone, Copy)] -struct TransactionState { - /// True if the transaction was previously broadcasted. - broadcasted: bool, -} - -impl TransactionState { - /// Construct a new [`TransactionState`]. - pub fn new() -> Self { - TransactionState { broadcasted: false } - } - - /// Handle events generated by the transaction-pool and convert them - /// to the new API expected state. - #[inline] - pub fn handle_event( - &mut self, - event: TransactionStatus, - ) -> Option> { - match event { - TransactionStatus::Ready | TransactionStatus::Future => - Some(TransactionEvent::::Validated), - TransactionStatus::Broadcast(peers) => { - // Set the broadcasted flag once if we submitted the transaction to - // at least one peer. - self.broadcasted = self.broadcasted || !peers.is_empty(); - - Some(TransactionEvent::Broadcasted(TransactionBroadcasted { - num_peers: peers.len(), - })) - }, - TransactionStatus::InBlock((hash, index)) => - Some(TransactionEvent::BestChainBlockIncluded(Some(TransactionBlock { - hash, - index, - }))), - TransactionStatus::Retracted(_) => Some(TransactionEvent::BestChainBlockIncluded(None)), - TransactionStatus::FinalityTimeout(_) => - Some(TransactionEvent::Dropped(TransactionDropped { - broadcasted: self.broadcasted, - error: "Maximum number of finality watchers has been reached".into(), - })), - TransactionStatus::Finalized((hash, index)) => - Some(TransactionEvent::Finalized(TransactionBlock { hash, index })), - TransactionStatus::Usurped(_) => Some(TransactionEvent::Invalid(TransactionError { - error: "Extrinsic was rendered invalid by another extrinsic".into(), - })), - TransactionStatus::Dropped => Some(TransactionEvent::Invalid(TransactionError { - error: "Extrinsic dropped from the pool due to exceeding limits".into(), - })), - TransactionStatus::Invalid => Some(TransactionEvent::Invalid(TransactionError { - error: "Extrinsic marked as invalid".into(), +/// Handle events generated by the transaction-pool and convert them +/// to the new API expected state. +#[inline] +pub fn handle_event( + event: TransactionStatus, +) -> Option> { + match event { + TransactionStatus::Ready | TransactionStatus::Future => + Some(TransactionEvent::::Validated), + TransactionStatus::InBlock((hash, index)) => + Some(TransactionEvent::BestChainBlockIncluded(Some(TransactionBlock { hash, index }))), + TransactionStatus::Retracted(_) => Some(TransactionEvent::BestChainBlockIncluded(None)), + TransactionStatus::FinalityTimeout(_) => + Some(TransactionEvent::Dropped(TransactionDropped { + error: "Maximum number of finality watchers has been reached".into(), })), - } + TransactionStatus::Finalized((hash, index)) => + Some(TransactionEvent::Finalized(TransactionBlock { hash, index })), + TransactionStatus::Usurped(_) => Some(TransactionEvent::Invalid(TransactionError { + error: "Extrinsic was rendered invalid by another extrinsic".into(), + })), + TransactionStatus::Dropped => Some(TransactionEvent::Invalid(TransactionError { + error: "Extrinsic dropped from the pool due to exceeding limits".into(), + })), + TransactionStatus::Invalid => Some(TransactionEvent::Invalid(TransactionError { + error: "Extrinsic marked as invalid".into(), + })), + // These are the events that are not supported by the new API. + TransactionStatus::Broadcast(_) => None, } } diff --git a/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs b/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs new file mode 100644 index 0000000000000000000000000000000000000000..92c838261874a816335b6d7e82c7df7667e2c235 --- /dev/null +++ b/substrate/client/rpc-spec-v2/src/transaction/transaction_broadcast.rs @@ -0,0 +1,251 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! API implementation for broadcasting transactions. + +use crate::{transaction::api::TransactionBroadcastApiServer, SubscriptionTaskExecutor}; +use codec::Decode; +use futures::{FutureExt, Stream, StreamExt}; +use futures_util::stream::AbortHandle; +use jsonrpsee::core::{async_trait, RpcResult}; +use parking_lot::RwLock; +use rand::{distributions::Alphanumeric, Rng}; +use sc_client_api::BlockchainEvents; +use sc_transaction_pool_api::{ + error::IntoPoolError, TransactionFor, TransactionPool, TransactionSource, +}; +use sp_blockchain::HeaderBackend; +use sp_core::Bytes; +use sp_runtime::traits::Block as BlockT; +use std::{collections::HashMap, sync::Arc}; + +use super::error::ErrorBroadcast; + +/// An API for transaction RPC calls. +pub struct TransactionBroadcast { + /// Substrate client. + client: Arc, + /// Transactions pool. + pool: Arc, + /// Executor to spawn subscriptions. + executor: SubscriptionTaskExecutor, + /// The brodcast operation IDs. + broadcast_ids: Arc>>, +} + +/// The state of a broadcast operation. +struct BroadcastState { + /// Handle to abort the running future that broadcasts the transaction. + handle: AbortHandle, +} + +impl TransactionBroadcast { + /// Creates a new [`TransactionBroadcast`]. + pub fn new(client: Arc, pool: Arc, executor: SubscriptionTaskExecutor) -> Self { + TransactionBroadcast { client, pool, executor, broadcast_ids: Default::default() } + } + + /// Generate an unique operation ID for the `transaction_broadcast` RPC method. + pub fn generate_unique_id(&self) -> String { + let generate_operation_id = || { + // The length of the operation ID. + const OPERATION_ID_LEN: usize = 16; + + rand::thread_rng() + .sample_iter(Alphanumeric) + .take(OPERATION_ID_LEN) + .map(char::from) + .collect::() + }; + + let mut id = generate_operation_id(); + + let broadcast_ids = self.broadcast_ids.read(); + + while broadcast_ids.contains_key(&id) { + id = generate_operation_id(); + } + + id + } +} + +/// Currently we treat all RPC transactions as externals. +/// +/// Possibly in the future we could allow opt-in for special treatment +/// of such transactions, so that the block authors can inject +/// some unique transactions via RPC and have them included in the pool. +const TX_SOURCE: TransactionSource = TransactionSource::External; + +#[async_trait] +impl TransactionBroadcastApiServer for TransactionBroadcast +where + Pool: TransactionPool + Sync + Send + 'static, + Pool::Error: IntoPoolError, + ::Hash: Unpin, + Client: HeaderBackend + BlockchainEvents + Send + Sync + 'static, +{ + fn broadcast(&self, bytes: Bytes) -> RpcResult> { + let pool = self.pool.clone(); + + // The unique ID of this operation. + let id = self.generate_unique_id(); + + let mut best_block_import_stream = + Box::pin(self.client.import_notification_stream().filter_map( + |notification| async move { notification.is_new_best.then_some(notification.hash) }, + )); + + let broadcast_transaction_fut = async move { + // There is nothing we could do with an extrinsic of invalid format. + let Ok(decoded_extrinsic) = TransactionFor::::decode(&mut &bytes[..]) else { + return; + }; + + // Flag to determine if the we should broadcast the transaction again. + let mut is_done = false; + + while !is_done { + // Wait for the last block to become available. + let Some(best_block_hash) = + last_stream_element(&mut best_block_import_stream).await + else { + return; + }; + + let mut stream = match pool + .submit_and_watch(best_block_hash, TX_SOURCE, decoded_extrinsic.clone()) + .await + { + Ok(stream) => stream, + // The transaction was not included to the pool. + Err(e) => { + let Ok(pool_err) = e.into_pool_error() else { return }; + + if pool_err.is_retriable() { + // Try to resubmit the transaction at a later block for + // recoverable errors. + continue + } else { + return; + } + }, + }; + + while let Some(event) = stream.next().await { + // Check if the transaction could be submitted again + // at a later time. + if event.is_retriable() { + break; + } + + // Stop if this is the final event of the transaction stream + // and the event is not retriable. + if event.is_final() { + is_done = true; + break; + } + } + } + }; + + // Convert the future into an abortable future, for easily terminating it from the + // `transaction_stop` method. + let (fut, handle) = futures::future::abortable(broadcast_transaction_fut); + let broadcast_ids = self.broadcast_ids.clone(); + let drop_id = id.clone(); + // The future expected by the executor must be `Future` instead of + // `Future>`. + let fut = fut.map(move |_| { + // Remove the entry from the broadcast IDs map. + broadcast_ids.write().remove(&drop_id); + }); + + // Keep track of this entry and the abortable handle. + { + let mut broadcast_ids = self.broadcast_ids.write(); + broadcast_ids.insert(id.clone(), BroadcastState { handle }); + } + + sc_rpc::utils::spawn_subscription_task(&self.executor, fut); + + Ok(Some(id)) + } + + fn stop_broadcast(&self, operation_id: String) -> Result<(), ErrorBroadcast> { + let mut broadcast_ids = self.broadcast_ids.write(); + + let Some(broadcast_state) = broadcast_ids.remove(&operation_id) else { + return Err(ErrorBroadcast::InvalidOperationID) + }; + + broadcast_state.handle.abort(); + + Ok(()) + } +} + +/// Returns the last element of the providided stream, or `None` if the stream is closed. +async fn last_stream_element(stream: &mut S) -> Option +where + S: Stream + Unpin, +{ + let Some(mut element) = stream.next().await else { return None }; + + // We are effectively polling the stream for the last available item at this time. + // The `now_or_never` returns `None` if the stream is `Pending`. + // + // If the stream contains `Hash0x1 Hash0x2 Hash0x3 Hash0x4`, we want only `Hash0x4`. + while let Some(next) = stream.next().now_or_never() { + let Some(next) = next else { + // Nothing to do if the stream terminated. + return None + }; + element = next; + } + + Some(element) +} + +#[cfg(test)] +mod tests { + use super::*; + use tokio_stream::wrappers::ReceiverStream; + + #[tokio::test] + async fn check_last_stream_element() { + let (tx, rx) = tokio::sync::mpsc::channel(16); + + let mut stream = ReceiverStream::new(rx); + // Check the stream with one element queued. + tx.send(1).await.unwrap(); + assert_eq!(last_stream_element(&mut stream).await, Some(1)); + + // Check the stream with multiple elements. + tx.send(1).await.unwrap(); + tx.send(2).await.unwrap(); + tx.send(3).await.unwrap(); + assert_eq!(last_stream_element(&mut stream).await, Some(3)); + + // Drop the stream with some elements + tx.send(1).await.unwrap(); + tx.send(2).await.unwrap(); + drop(tx); + assert_eq!(last_stream_element(&mut stream).await, None); + } +} diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index 6917eb0b551a98253c7908b353d8c40a581ff3b7..f65e6c9a59ec1c6c593d00c7639ca4bef26e4319 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -jsonrpsee = { version = "0.20.3", features = ["server"] } -log = "0.4.17" +jsonrpsee = { version = "0.22", features = ["server"] } +log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } sc-block-builder = { path = "../block-builder" } sc-chain-spec = { path = "../chain-spec" } sc-client-api = { path = "../api" } diff --git a/substrate/client/rpc/src/author/tests.rs b/substrate/client/rpc/src/author/tests.rs index 471016a015da124e839c65e54e73d58ad3ff22a4..937870eb53fd9b2ec4800c8eca343bb111560eef 100644 --- a/substrate/client/rpc/src/author/tests.rs +++ b/substrate/client/rpc/src/author/tests.rs @@ -21,10 +21,7 @@ use super::*; use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; use codec::Encode; -use jsonrpsee::{ - core::{EmptyServerParams as EmptyParams, Error as RpcError}, - RpcModule, -}; +use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError, RpcModule}; use sc_transaction_pool::{BasicPool, FullChainApi}; use sc_transaction_pool_api::TransactionStatus; use sp_core::{ @@ -103,7 +100,7 @@ async fn author_submit_transaction_should_not_cause_error() { assert_matches!( api.call::<_, H256>("author_submitExtrinsic", [xt]).await, - Err(RpcError::Call(err)) if err.message().contains("Already Imported") && err.code() == 1013 + Err(RpcError::JsonRpc(err)) if err.message().contains("Already Imported") && err.code() == 1013 ); } @@ -160,7 +157,7 @@ async fn author_should_return_watch_validation_error() { assert_matches!( failed_sub, - Err(RpcError::Call(err)) if err.message().contains("Invalid Transaction") && err.code() == 1010 + Err(RpcError::JsonRpc(err)) if err.message().contains("Invalid Transaction") && err.code() == 1010 ); } @@ -276,7 +273,7 @@ async fn author_has_session_keys() { assert_matches!( api.call::<_, bool>("author_hasSessionKeys", vec![Bytes::from(vec![1, 2, 3])]).await, - Err(RpcError::Call(err)) if err.message().contains("Session keys are not encoded correctly") + Err(RpcError::JsonRpc(err)) if err.message().contains("Session keys are not encoded correctly") ); } diff --git a/substrate/client/rpc/src/dev/tests.rs b/substrate/client/rpc/src/dev/tests.rs index 5eb4897056cc438cc06bc3332056681136f82a9e..e8f9ba4990d255789cda8c92c17f71cca924399f 100644 --- a/substrate/client/rpc/src/dev/tests.rs +++ b/substrate/client/rpc/src/dev/tests.rs @@ -100,7 +100,7 @@ async fn deny_unsafe_works() { let (resp, _) = api.raw_json_request(&request, 1).await.expect("Raw calls should succeed"); assert_eq!( - resp.result, + resp, r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"# ); } diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs index 25a34faed9a66c2cedadedfc1b71a397f37c4131..96f4c1be960faa5f15508f19a9dd5665d8b684c1 100644 --- a/substrate/client/rpc/src/state/tests.rs +++ b/substrate/client/rpc/src/state/tests.rs @@ -21,7 +21,7 @@ use super::*; use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; use futures::executor; -use jsonrpsee::core::{EmptyServerParams as EmptyParams, Error as RpcError}; +use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError}; use sc_block_builder::BlockBuilderBuilder; use sc_rpc_api::DenyUnsafe; use sp_consensus::BlockOrigin; @@ -525,7 +525,7 @@ async fn wildcard_storage_subscriptions_are_rpc_unsafe() { let api_rpc = api.into_rpc(); let err = api_rpc.subscribe_unbounded("state_subscribeStorage", EmptyParams::new()).await; - assert_matches!(err, Err(RpcError::Call(e)) if e.message() == "RPC call is unsafe to be called externally"); + assert_matches!(err, Err(RpcError::JsonRpc(e)) if e.message() == "RPC call is unsafe to be called externally"); } #[tokio::test] diff --git a/substrate/client/rpc/src/system/tests.rs b/substrate/client/rpc/src/system/tests.rs index 21d13ccfafaa7fc46d6726966f66aa14f96fc595..03967c63523c79610009b0edb4ac4e6618b99040 100644 --- a/substrate/client/rpc/src/system/tests.rs +++ b/substrate/client/rpc/src/system/tests.rs @@ -19,10 +19,7 @@ use super::{helpers::SyncState, *}; use assert_matches::assert_matches; use futures::prelude::*; -use jsonrpsee::{ - core::{EmptyServerParams as EmptyParams, Error as RpcError}, - RpcModule, -}; +use jsonrpsee::{core::EmptyServerParams as EmptyParams, MethodsError as RpcError, RpcModule}; use sc_network::{self, config::Role, PeerId}; use sc_rpc_api::system::helpers::PeerInfo; use sc_utils::mpsc::tracing_unbounded; @@ -311,7 +308,7 @@ async fn system_network_add_reserved() { let bad_peer_id = ["/ip4/198.51.100.19/tcp/30333"]; assert_matches!( api(None).call::<_, ()>("system_addReservedPeer", bad_peer_id).await, - Err(RpcError::Call(err)) if err.message().contains("Peer id is missing from the address") + Err(RpcError::JsonRpc(err)) if err.message().contains("Peer id is missing from the address") ); } @@ -327,7 +324,7 @@ async fn system_network_remove_reserved() { assert_matches!( api(None).call::<_, String>("system_removeReservedPeer", bad_peer_id).await, - Err(RpcError::Call(err)) if err.message().contains("base-58 decode error: provided string contained invalid character '/' at byte 0") + Err(RpcError::JsonRpc(err)) if err.message().contains("base-58 decode error: provided string contained invalid character '/' at byte 0") ); } #[tokio::test] diff --git a/substrate/client/rpc/src/utils.rs b/substrate/client/rpc/src/utils.rs index b5ae4a2b6bc7fed26be4faf7c6ae9383dfdabd5f..6ec48efef846c719db4e009b3a14926db0577d5f 100644 --- a/substrate/client/rpc/src/utils.rs +++ b/substrate/client/rpc/src/utils.rs @@ -80,7 +80,7 @@ where Either::Left((Ok(sink), _)) => break sink, Either::Right((Some(msg), f)) => { if buf.push_back(msg).is_err() { - log::warn!(target: "rpc", "Subscription::accept failed buffer limit={} exceed; dropping subscription", buf.max_cap); + log::warn!(target: "rpc", "Subscription::accept failed buffer limit={} exceeded; dropping subscription", buf.max_cap); return } accept_fut = f; @@ -125,7 +125,13 @@ async fn inner_pipe_from_stream( // New item from the stream Either::Right((Either::Right((Some(v), n)), c)) => { if buf.push_back(v).is_err() { - log::warn!(target: "rpc", "Subscription buffer limit={} exceed; dropping subscription", buf.max_cap); + log::warn!( + target: "rpc", + "Subscription buffer limit={} exceeded for subscription={} conn_id={}; dropping subscription", + buf.max_cap, + sink.method_name(), + sink.connection_id() + ); return } diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index 1c95112aa6b0e714eeb0ea4cc24c76a026bf60b4..bbf67d1fbd0af6dceada3b73178004558044d0b2 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -28,17 +28,17 @@ runtime-benchmarks = [ ] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["server"] } -thiserror = "1.0.48" +jsonrpsee = { version = "0.22", features = ["server"] } +thiserror = { workspace = true } futures = "0.3.21" rand = "0.8.5" parking_lot = "0.12.1" -log = "0.4.17" +log = { workspace = true, default-features = true } futures-timer = "3.0.1" exit-future = "0.2.0" pin-project = "1.0.12" -serde = "1.0.195" -serde_json = "1.0.111" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sc-keystore = { path = "../keystore" } sp-runtime = { path = "../../primitives/runtime" } sp-trie = { path = "../../primitives/trie" } @@ -84,6 +84,7 @@ tokio = { version = "1.22.0", features = ["parking_lot", "rt-multi-thread", "tim tempfile = "3.1.0" directories = "5.0.1" static_init = "1.0.3" +schnellru = "0.2.1" [dev-dependencies] substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index bc848e8d4b293c2334c290683d757e0a03b9dd87..31d63c6a81d3a4df2e6eca32260a64f577712aa9 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -460,7 +460,7 @@ where spawn_handle.spawn( "on-transaction-imported", Some("transaction-pool"), - transaction_notifications( + propagate_transaction_notifications( transaction_pool.clone(), tx_handler_controller, telemetry.clone(), @@ -532,7 +532,8 @@ where Ok(rpc_handlers) } -async fn transaction_notifications( +/// Returns a future that forwards imported transactions to the transaction networking protocol. +pub async fn propagate_transaction_notifications( transaction_pool: Arc, tx_handler_controller: sc_network_transactions::TransactionsHandlerController< ::Hash, @@ -560,7 +561,8 @@ async fn transaction_notifications( .await; } -fn init_telemetry( +/// Initialize telemetry with provided configuration and return telemetry handle +pub fn init_telemetry( config: &mut Configuration, network: Network, client: Arc, @@ -598,7 +600,8 @@ where Ok(telemetry.handle()) } -fn gen_rpc_module( +/// Generate RPC module using provided configuration +pub fn gen_rpc_module( deny_unsafe: DenyUnsafe, spawn_handle: SpawnTaskHandle, client: Arc, diff --git a/substrate/client/service/src/client/client.rs b/substrate/client/service/src/client/client.rs index aa9c1b80a29a95bd77efbda35620c132b624bd9b..35e8b53a09cf2d802a0c4a873f7ecb8099764e83 100644 --- a/substrate/client/service/src/client/client.rs +++ b/substrate/client/service/src/client/client.rs @@ -19,8 +19,8 @@ //! Substrate Client use super::block_rules::{BlockRules, LookupResult as BlockLookupResult}; -use futures::{FutureExt, StreamExt}; -use log::{error, info, trace, warn}; +use crate::client::notification_pinning::NotificationPinningWorker; +use log::{debug, info, trace, warn}; use parking_lot::{Mutex, RwLock}; use prometheus_endpoint::Registry; use rand::Rng; @@ -38,7 +38,7 @@ use sc_client_api::{ execution_extensions::ExecutionExtensions, notifications::{StorageEventStream, StorageNotifications}, CallExecutor, ExecutorProvider, KeysIter, OnFinalityAction, OnImportAction, PairsIter, - ProofProvider, UsageProvider, + ProofProvider, UnpinWorkerMessage, UsageProvider, }; use sc_consensus::{ BlockCheckParams, BlockImportParams, ForkChoiceStrategy, ImportResult, StateAction, @@ -114,7 +114,7 @@ where block_rules: BlockRules, config: ClientConfig, telemetry: Option, - unpin_worker_sender: TracingUnboundedSender, + unpin_worker_sender: TracingUnboundedSender>, _phantom: PhantomData, } @@ -326,19 +326,35 @@ where // dropped, the block will be unpinned automatically. if let Some(ref notification) = finality_notification { if let Err(err) = self.backend.pin_block(notification.hash) { - error!( + debug!( "Unable to pin block for finality notification. hash: {}, Error: {}", notification.hash, err ); - }; + } else { + let _ = self + .unpin_worker_sender + .unbounded_send(UnpinWorkerMessage::AnnouncePin(notification.hash)) + .map_err(|e| { + log::error!( + "Unable to send AnnouncePin worker message for finality: {e}" + ) + }); + } } if let Some(ref notification) = import_notification { if let Err(err) = self.backend.pin_block(notification.hash) { - error!( + debug!( "Unable to pin block for import notification. hash: {}, Error: {}", notification.hash, err ); + } else { + let _ = self + .unpin_worker_sender + .unbounded_send(UnpinWorkerMessage::AnnouncePin(notification.hash)) + .map_err(|e| { + log::error!("Unable to send AnnouncePin worker message for import: {e}") + }); }; } @@ -416,25 +432,12 @@ where backend.commit_operation(op)?; } - let (unpin_worker_sender, mut rx) = - tracing_unbounded::("unpin-worker-channel", 10_000); - let task_backend = Arc::downgrade(&backend); - spawn_handle.spawn( - "unpin-worker", - None, - async move { - while let Some(message) = rx.next().await { - if let Some(backend) = task_backend.upgrade() { - backend.unpin_block(message); - } else { - log::debug!("Terminating unpin-worker, backend reference was dropped."); - return - } - } - log::debug!("Terminating unpin-worker, stream terminated.") - } - .boxed(), + let (unpin_worker_sender, rx) = tracing_unbounded::>( + "notification-pinning-worker-channel", + 10_000, ); + let unpin_worker = NotificationPinningWorker::new(rx, backend.clone()); + spawn_handle.spawn("notification-pinning-worker", None, Box::pin(unpin_worker.run())); Ok(Client { backend, @@ -675,8 +678,10 @@ where // This is use by fast sync for runtime version to be resolvable from // changes. - let state_version = - resolve_state_version_from_wasm(&storage, &self.executor)?; + let state_version = resolve_state_version_from_wasm::<_, HashingFor>( + &storage, + &self.executor, + )?; let state_root = operation.op.reset_storage(storage, state_version)?; if state_root != *import_headers.post().state_root() { // State root mismatch when importing state. This should not happen in diff --git a/substrate/client/service/src/client/mod.rs b/substrate/client/service/src/client/mod.rs index a13fd4317e1553d379ea068c516e74072d3c8c95..0703cc2b47d144d4e67418cfb9966cd1cd209392 100644 --- a/substrate/client/service/src/client/mod.rs +++ b/substrate/client/service/src/client/mod.rs @@ -47,6 +47,7 @@ mod block_rules; mod call_executor; mod client; +mod notification_pinning; mod wasm_override; mod wasm_substitutes; diff --git a/substrate/client/service/src/client/notification_pinning.rs b/substrate/client/service/src/client/notification_pinning.rs new file mode 100644 index 0000000000000000000000000000000000000000..80de91c02f1ae0273d42edc79d442aa312d09356 --- /dev/null +++ b/substrate/client/service/src/client/notification_pinning.rs @@ -0,0 +1,353 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Notification pinning related logic. +//! +//! This file contains a worker that should be started when a new client instance is created. +//! The goal is to avoid pruning of blocks that have active notifications in the node. Every +//! recipient of notifications should receive the chance to act upon them. In addition, notification +//! listeners can hold onto a [`sc_client_api::UnpinHandle`] to keep a block pinned. Once the handle +//! is dropped, a message is sent and the worker unpins the respective block. +use std::{ + marker::PhantomData, + sync::{Arc, Weak}, +}; + +use futures::StreamExt; +use sc_client_api::{Backend, UnpinWorkerMessage}; + +use sc_utils::mpsc::TracingUnboundedReceiver; +use schnellru::Limiter; +use sp_runtime::traits::Block as BlockT; + +const LOG_TARGET: &str = "db::notification_pinning"; +const NOTIFICATION_PINNING_LIMIT: usize = 1024; + +/// A limiter which automatically unpins blocks that leave the data structure. +#[derive(Clone, Debug)] +struct UnpinningByLengthLimiter> { + max_length: usize, + backend: Weak, + _phantom: PhantomData, +} + +impl> UnpinningByLengthLimiter { + /// Creates a new length limiter with a given `max_length`. + pub fn new(max_length: usize, backend: Weak) -> UnpinningByLengthLimiter { + UnpinningByLengthLimiter { max_length, backend, _phantom: PhantomData::::default() } + } +} + +impl> Limiter + for UnpinningByLengthLimiter +{ + type KeyToInsert<'a> = Block::Hash; + type LinkType = usize; + + fn is_over_the_limit(&self, length: usize) -> bool { + length > self.max_length + } + + fn on_insert( + &mut self, + _length: usize, + key: Self::KeyToInsert<'_>, + value: u32, + ) -> Option<(Block::Hash, u32)> { + log::debug!(target: LOG_TARGET, "Pinning block based on notification. hash = {key}"); + if self.max_length > 0 { + Some((key, value)) + } else { + None + } + } + + fn on_replace( + &mut self, + _length: usize, + _old_key: &mut Block::Hash, + _new_key: Block::Hash, + _old_value: &mut u32, + _new_value: &mut u32, + ) -> bool { + true + } + + fn on_removed(&mut self, key: &mut Block::Hash, references: &mut u32) { + // If reference count was larger than 0 on removal, + // the item was removed due to capacity limitations. + // Since the cache should be large enough for pinned items, + // we want to know about these evictions. + if *references > 0 { + log::warn!( + target: LOG_TARGET, + "Notification block pinning limit reached. Unpinning block with hash = {key:?}" + ); + if let Some(backend) = self.backend.upgrade() { + (0..*references).for_each(|_| backend.unpin_block(*key)); + } + } else { + log::trace!( + target: LOG_TARGET, + "Unpinned block. hash = {key:?}", + ) + } + } + + fn on_cleared(&mut self) {} + + fn on_grow(&mut self, _new_memory_usage: usize) -> bool { + true + } +} + +/// Worker for the handling of notification pinning. +/// +/// It receives messages from a receiver and pins/unpins based on the incoming messages. +/// All notification related unpinning should go through this worker. If the maximum number of +/// notification pins is reached, the block from the oldest notification is unpinned. +pub struct NotificationPinningWorker> { + unpin_message_rx: TracingUnboundedReceiver>, + task_backend: Weak, + pinned_blocks: schnellru::LruMap>, +} + +impl> NotificationPinningWorker { + /// Creates a new `NotificationPinningWorker`. + pub fn new( + unpin_message_rx: TracingUnboundedReceiver>, + task_backend: Arc, + ) -> Self { + let pinned_blocks = + schnellru::LruMap::>::new( + UnpinningByLengthLimiter::new( + NOTIFICATION_PINNING_LIMIT, + Arc::downgrade(&task_backend), + ), + ); + Self { unpin_message_rx, task_backend: Arc::downgrade(&task_backend), pinned_blocks } + } + + fn handle_announce_message(&mut self, hash: Block::Hash) { + if let Some(entry) = self.pinned_blocks.get_or_insert(hash, Default::default) { + *entry = *entry + 1; + } + } + + fn handle_unpin_message(&mut self, hash: Block::Hash) -> Result<(), ()> { + if let Some(refcount) = self.pinned_blocks.peek_mut(&hash) { + *refcount = *refcount - 1; + if *refcount == 0 { + self.pinned_blocks.remove(&hash); + } + if let Some(backend) = self.task_backend.upgrade() { + log::debug!(target: LOG_TARGET, "Reducing pinning refcount for block hash = {hash:?}"); + backend.unpin_block(hash); + } else { + log::debug!(target: LOG_TARGET, "Terminating unpin-worker, backend reference was dropped."); + return Err(()) + } + } else { + log::debug!(target: LOG_TARGET, "Received unpin message for already unpinned block. hash = {hash:?}"); + } + Ok(()) + } + + /// Start working on the received messages. + /// + /// The worker maintains a map which keeps track of the pinned blocks and their reference count. + /// Depending upon the received message, it acts to pin/unpin the block. + pub async fn run(mut self) { + while let Some(message) = self.unpin_message_rx.next().await { + match message { + UnpinWorkerMessage::AnnouncePin(hash) => self.handle_announce_message(hash), + UnpinWorkerMessage::Unpin(hash) => + if self.handle_unpin_message(hash).is_err() { + return + }, + } + } + log::debug!(target: LOG_TARGET, "Terminating unpin-worker, stream terminated.") + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use sc_client_api::{Backend, UnpinWorkerMessage}; + use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver}; + use sp_core::H256; + use sp_runtime::traits::Block as BlockT; + + type Block = substrate_test_runtime_client::runtime::Block; + + use super::{NotificationPinningWorker, UnpinningByLengthLimiter}; + + impl> NotificationPinningWorker { + fn new_with_limit( + unpin_message_rx: TracingUnboundedReceiver>, + task_backend: Arc, + limit: usize, + ) -> Self { + let pinned_blocks = + schnellru::LruMap::>::new( + UnpinningByLengthLimiter::new(limit, Arc::downgrade(&task_backend)), + ); + Self { unpin_message_rx, task_backend: Arc::downgrade(&task_backend), pinned_blocks } + } + + fn lru( + &self, + ) -> &schnellru::LruMap> { + &self.pinned_blocks + } + } + + #[test] + fn pinning_worker_handles_base_case() { + let (_tx, rx) = tracing_unbounded("testing", 1000); + + let backend = Arc::new(sc_client_api::in_mem::Backend::::new()); + + let hash = H256::random(); + + let mut worker = NotificationPinningWorker::new(rx, backend.clone()); + + // Block got pinned and unpin message should unpin in the backend. + let _ = backend.pin_block(hash); + assert_eq!(backend.pin_refs(&hash), Some(1)); + + worker.handle_announce_message(hash); + assert_eq!(worker.lru().len(), 1); + + let _ = worker.handle_unpin_message(hash); + + assert_eq!(backend.pin_refs(&hash), Some(0)); + assert!(worker.lru().is_empty()); + } + + #[test] + fn pinning_worker_handles_multiple_pins() { + let (_tx, rx) = tracing_unbounded("testing", 1000); + + let backend = Arc::new(sc_client_api::in_mem::Backend::::new()); + + let hash = H256::random(); + + let mut worker = NotificationPinningWorker::new(rx, backend.clone()); + // Block got pinned multiple times. + let _ = backend.pin_block(hash); + let _ = backend.pin_block(hash); + let _ = backend.pin_block(hash); + assert_eq!(backend.pin_refs(&hash), Some(3)); + + worker.handle_announce_message(hash); + worker.handle_announce_message(hash); + worker.handle_announce_message(hash); + assert_eq!(worker.lru().len(), 1); + + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(2)); + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(1)); + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(0)); + assert!(worker.lru().is_empty()); + + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(0)); + } + + #[test] + fn pinning_worker_handles_too_many_unpins() { + let (_tx, rx) = tracing_unbounded("testing", 1000); + + let backend = Arc::new(sc_client_api::in_mem::Backend::::new()); + + let hash = H256::random(); + let hash2 = H256::random(); + + let mut worker = NotificationPinningWorker::new(rx, backend.clone()); + // Block was announced once but unpinned multiple times. The worker should ignore the + // additional unpins. + let _ = backend.pin_block(hash); + let _ = backend.pin_block(hash); + let _ = backend.pin_block(hash); + assert_eq!(backend.pin_refs(&hash), Some(3)); + + worker.handle_announce_message(hash); + assert_eq!(worker.lru().len(), 1); + + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(2)); + let _ = worker.handle_unpin_message(hash); + assert_eq!(backend.pin_refs(&hash), Some(2)); + assert!(worker.lru().is_empty()); + + let _ = worker.handle_unpin_message(hash2); + assert!(worker.lru().is_empty()); + assert_eq!(backend.pin_refs(&hash2), None); + } + + #[test] + fn pinning_worker_should_evict_when_limit_reached() { + let (_tx, rx) = tracing_unbounded("testing", 1000); + + let backend = Arc::new(sc_client_api::in_mem::Backend::::new()); + + let hash1 = H256::random(); + let hash2 = H256::random(); + let hash3 = H256::random(); + let hash4 = H256::random(); + + // Only two items fit into the cache. + let mut worker = NotificationPinningWorker::new_with_limit(rx, backend.clone(), 2); + + // Multiple blocks are announced but the cache size is too small. We expect that blocks + // are evicted by the cache and unpinned in the backend. + let _ = backend.pin_block(hash1); + let _ = backend.pin_block(hash2); + let _ = backend.pin_block(hash3); + assert_eq!(backend.pin_refs(&hash1), Some(1)); + assert_eq!(backend.pin_refs(&hash2), Some(1)); + assert_eq!(backend.pin_refs(&hash3), Some(1)); + + worker.handle_announce_message(hash1); + assert!(worker.lru().peek(&hash1).is_some()); + worker.handle_announce_message(hash2); + assert!(worker.lru().peek(&hash2).is_some()); + worker.handle_announce_message(hash3); + assert!(worker.lru().peek(&hash3).is_some()); + assert!(worker.lru().peek(&hash2).is_some()); + assert_eq!(worker.lru().len(), 2); + + // Hash 1 should have gotten unpinned, since its oldest. + assert_eq!(backend.pin_refs(&hash1), Some(0)); + assert_eq!(backend.pin_refs(&hash2), Some(1)); + assert_eq!(backend.pin_refs(&hash3), Some(1)); + + // Hash 2 is getting bumped. + worker.handle_announce_message(hash2); + assert_eq!(worker.lru().peek(&hash2), Some(&2)); + + // Since hash 2 was accessed, evict hash 3. + worker.handle_announce_message(hash4); + assert_eq!(worker.lru().peek(&hash3), None); + } +} diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs index 3e68f5b58defc40b802b359e68b116a1fd734e15..35262ff493b44f07a94f1d35f1a3e765251a1897 100644 --- a/substrate/client/service/src/config.rs +++ b/substrate/client/service/src/config.rs @@ -18,6 +18,7 @@ //! Service configuration. +pub use jsonrpsee::server::BatchRequestConfig as RpcBatchRequestConfig; use prometheus_endpoint::Registry; use sc_chain_spec::ChainSpec; pub use sc_client_db::{BlocksPruning, Database, DatabaseSource, PruningMode}; @@ -39,6 +40,7 @@ use sp_core::crypto::SecretString; use std::{ io, iter, net::SocketAddr, + num::NonZeroU32, path::{Path, PathBuf}, }; use tempfile::TempDir; @@ -102,6 +104,10 @@ pub struct Configuration { pub rpc_port: u16, /// The number of messages the JSON-RPC server is allowed to keep in memory. pub rpc_message_buffer_capacity: u32, + /// JSON-RPC server batch config. + pub rpc_batch_config: RpcBatchRequestConfig, + /// RPC rate limit per minute. + pub rpc_rate_limit: Option, /// Prometheus endpoint configuration. `None` if disabled. pub prometheus_config: Option, /// Telemetry service URL. `None` if disabled. diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 875cb9ca79e2056e18873f1308c45ce087cb7dd4..9480d4a0b07276a5131be17579964c04abda1f1d 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -38,7 +38,7 @@ use std::{collections::HashMap, net::SocketAddr}; use codec::{Decode, Encode}; use futures::{pin_mut, FutureExt, StreamExt}; -use jsonrpsee::{core::Error as JsonRpseeError, RpcModule}; +use jsonrpsee::RpcModule; use log::{debug, error, warn}; use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider}; use sc_network::{ @@ -109,17 +109,14 @@ impl RpcHandlers { pub async fn rpc_query( &self, json_query: &str, - ) -> Result<(String, tokio::sync::mpsc::Receiver), JsonRpseeError> { + ) -> Result<(String, tokio::sync::mpsc::Receiver), serde_json::Error> { // Because `tokio::sync::mpsc::channel` is used under the hood // it will panic if it's set to usize::MAX. // // This limit is used to prevent panics and is large enough. const TOKIO_MPSC_MAX_SIZE: usize = tokio::sync::Semaphore::MAX_PERMITS; - self.0 - .raw_json_request(json_query, TOKIO_MPSC_MAX_SIZE) - .await - .map(|(method_res, recv)| (method_res.result, recv)) + self.0.raw_json_request(json_query, TOKIO_MPSC_MAX_SIZE).await } /// Provides access to the underlying `RpcModule` @@ -368,7 +365,7 @@ mod waiting { } /// Starts RPC servers. -fn start_rpc_servers( +pub fn start_rpc_servers( config: &Configuration, gen_rpc_module: R, rpc_id_provider: Option>, @@ -396,6 +393,7 @@ where let server_config = sc_rpc_server::Config { addrs: [addr, backup_addr], + batch_config: config.rpc_batch_config, max_connections: config.rpc_max_connections, max_payload_in_mb: config.rpc_max_request_size, max_payload_out_mb: config.rpc_max_response_size, @@ -406,6 +404,7 @@ where id_provider: rpc_id_provider, cors: config.rpc_cors.as_ref(), tokio_handle: config.tokio_handle.clone(), + rate_limit: config.rpc_rate_limit, }; // TODO: https://github.com/paritytech/substrate/issues/13773 diff --git a/substrate/client/service/test/Cargo.toml b/substrate/client/service/test/Cargo.toml index 625d8286396e7778dd7271db772d143550dc6b5e..ee7e60f6011701f9d12fdb521db8226c883dbfe4 100644 --- a/substrate/client/service/test/Cargo.toml +++ b/substrate/client/service/test/Cargo.toml @@ -19,7 +19,7 @@ async-channel = "1.8.0" array-bytes = "6.1" fdlimit = "0.3.0" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = "3.6.1" parking_lot = "0.12.1" tempfile = "3.1.0" diff --git a/substrate/client/service/test/src/lib.rs b/substrate/client/service/test/src/lib.rs index 9b88300bf53048502f2579891886f2832ba6d530..349538965ee1fa04bd0acfff42b517ae40452c1c 100644 --- a/substrate/client/service/test/src/lib.rs +++ b/substrate/client/service/test/src/lib.rs @@ -29,7 +29,7 @@ use sc_network::{ use sc_network_sync::SyncingService; use sc_service::{ client::Client, - config::{BasePath, DatabaseSource, KeystoreConfig}, + config::{BasePath, DatabaseSource, KeystoreConfig, RpcBatchRequestConfig}, BlocksPruning, ChainSpecExtension, Configuration, Error, GenericChainSpec, Role, RuntimeGenesis, SpawnTaskHandle, TaskManager, }; @@ -254,6 +254,8 @@ fn node_config< rpc_max_subs_per_conn: Default::default(), rpc_port: 9944, rpc_message_buffer_capacity: Default::default(), + rpc_batch_config: RpcBatchRequestConfig::Unlimited, + rpc_rate_limit: None, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/client/state-db/Cargo.toml b/substrate/client/state-db/Cargo.toml index 3f86a0da88e4244573d43e19997ed6dadd05767f..400dda20c223443687292315b4974e5125570553 100644 --- a/substrate/client/state-db/Cargo.toml +++ b/substrate/client/state-db/Cargo.toml @@ -17,6 +17,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" sp-core = { path = "../../primitives/core" } diff --git a/substrate/client/statement-store/Cargo.toml b/substrate/client/statement-store/Cargo.toml index ed2292593790afbb61a20d588cda84c502189c7a..676f6cb36f67992c86db1174cbbf96e02e53d1b1 100644 --- a/substrate/client/statement-store/Cargo.toml +++ b/substrate/client/statement-store/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" parity-db = "0.4.12" tokio = { version = "1.22.0", features = ["time"] } diff --git a/substrate/client/storage-monitor/Cargo.toml b/substrate/client/storage-monitor/Cargo.toml index f4db58d6bb0ce364fd6c9402f1e7aa00f897cca9..b2120b3efc4396801bdbcaf0a7867ab6692908d3 100644 --- a/substrate/client/storage-monitor/Cargo.toml +++ b/substrate/client/storage-monitor/Cargo.toml @@ -12,9 +12,9 @@ homepage = "https://substrate.io" workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive", "string"] } -log = "0.4.17" +clap = { version = "4.5.1", features = ["derive", "string"] } +log = { workspace = true, default-features = true } fs4 = "0.7.0" sp-core = { path = "../../primitives/core" } tokio = { version = "1.22.0", features = ["time"] } -thiserror = "1.0.48" +thiserror = { workspace = true } diff --git a/substrate/client/sync-state-rpc/Cargo.toml b/substrate/client/sync-state-rpc/Cargo.toml index 328ecd17f75b371311437c708a070f9f8137bc08..09dc611caa044debeabce4f15686465535f076ed 100644 --- a/substrate/client/sync-state-rpc/Cargo.toml +++ b/substrate/client/sync-state-rpc/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" -thiserror = "1.0.48" +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } sc-chain-spec = { path = "../chain-spec" } sc-client-api = { path = "../api" } sc-consensus-babe = { path = "../consensus/babe" } diff --git a/substrate/client/sysinfo/Cargo.toml b/substrate/client/sysinfo/Cargo.toml index c09fa41d4df478b135315aea28516ed259d65d55..ba58452ffb58bfd737235beb285e6e46c992967d 100644 --- a/substrate/client/sysinfo/Cargo.toml +++ b/substrate/client/sysinfo/Cargo.toml @@ -19,13 +19,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.19" libc = "0.2" -log = "0.4.17" +log = { workspace = true, default-features = true } rand = "0.8.5" rand_pcg = "0.3.1" derive_more = "0.99" regex = "1" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sc-telemetry = { path = "../telemetry" } sp-core = { path = "../../primitives/core" } sp-crypto-hashing = { path = "../../primitives/crypto/hashing" } diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index 3270a2e148dbfe5ac2a83ddf6f7fdc1ebb2c279b..8ab00202f0ba6828dbf210e44d6337393ba1d391 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -20,12 +20,12 @@ targets = ["x86_64-unknown-linux-gnu"] chrono = "0.4.31" futures = "0.3.21" libp2p = { version = "0.51.4", features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" pin-project = "1.0.12" sc-utils = { path = "../utils" } rand = "0.8.5" -serde = { version = "1.0.195", features = ["derive"] } -serde_json = "1.0.111" -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } wasm-timer = "0.2.5" diff --git a/substrate/client/tracing/Cargo.toml b/substrate/client/tracing/Cargo.toml index 9f0f5fb6936fe98df880b1c62188ef77f881802d..61e6f7d0bab5b2422b3354edb10e1753d3ef1e7a 100644 --- a/substrate/client/tracing/Cargo.toml +++ b/substrate/client/tracing/Cargo.toml @@ -22,12 +22,12 @@ chrono = "0.4.31" codec = { package = "parity-scale-codec", version = "3.6.1" } lazy_static = "1.4.0" libc = "0.2.152" -log = { version = "0.4.17" } +log = { workspace = true, default-features = true } parking_lot = "0.12.1" regex = "1.6.0" rustc-hash = "1.1.0" -serde = "1.0.195" -thiserror = "1.0.48" +serde = { workspace = true, default-features = true } +thiserror = { workspace = true } tracing = "0.1.29" tracing-log = "0.1.3" tracing-subscriber = { version = "0.2.25", features = ["parking_lot"] } diff --git a/substrate/client/tracing/proc-macro/Cargo.toml b/substrate/client/tracing/proc-macro/Cargo.toml index 4a826d9f4fce07089a7c26e48621c2af6fb21e62..fec34aa0bca935e22f0f7d7faea17cac5d8f10bb 100644 --- a/substrate/client/tracing/proc-macro/Cargo.toml +++ b/substrate/client/tracing/proc-macro/Cargo.toml @@ -20,5 +20,5 @@ proc-macro = true [dependencies] proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = { version = "1.0.28", features = ["proc-macro"] } -syn = { version = "2.0.48", features = ["extra-traits", "full", "parsing", "proc-macro"] } +quote = { features = ["proc-macro"], workspace = true } +syn = { features = ["extra-traits", "full", "parsing", "proc-macro"], workspace = true } diff --git a/substrate/client/transaction-pool/Cargo.toml b/substrate/client/transaction-pool/Cargo.toml index b491f7bcafdabc53b172ed18cebf6f49d078391c..2ca37afd61b84a7228852290d5075841c993a503 100644 --- a/substrate/client/transaction-pool/Cargo.toml +++ b/substrate/client/transaction-pool/Cargo.toml @@ -21,10 +21,10 @@ codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.2" linked-hash-map = "0.5.4" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" -serde = { version = "1.0.195", features = ["derive"] } -thiserror = "1.0.48" +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } sc-client-api = { path = "../api" } sc-transaction-pool-api = { path = "api" } diff --git a/substrate/client/transaction-pool/api/Cargo.toml b/substrate/client/transaction-pool/api/Cargo.toml index f5fba65a06839e61347372b80af1226e7be142af..d52e4783fabce48992dec01e83c61bc7a3074a20 100644 --- a/substrate/client/transaction-pool/api/Cargo.toml +++ b/substrate/client/transaction-pool/api/Cargo.toml @@ -15,12 +15,12 @@ workspace = true async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -log = "0.4.17" -serde = { version = "1.0.195", features = ["derive"] } -thiserror = "1.0.48" +log = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +thiserror = { workspace = true } sp-blockchain = { path = "../../../primitives/blockchain" } sp-core = { path = "../../../primitives/core", default-features = false } sp-runtime = { path = "../../../primitives/runtime", default-features = false } [dev-dependencies] -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } diff --git a/substrate/client/transaction-pool/api/src/error.rs b/substrate/client/transaction-pool/api/src/error.rs index e521502f66fb1cbd3e85ef4b85ec9839d83e8d9f..d0744bfa3e192bcd5e6795ee96540c503c8ebec5 100644 --- a/substrate/client/transaction-pool/api/src/error.rs +++ b/substrate/client/transaction-pool/api/src/error.rs @@ -71,6 +71,30 @@ pub enum Error { RejectedFutureTransaction, } +impl Error { + /// Returns true if the transaction could be re-submitted to the pool in the future. + /// + /// For example, `Error::ImmediatelyDropped` is retriable, because the transaction + /// may enter the pool if there is space for it in the future. + pub fn is_retriable(&self) -> bool { + match self { + // An invalid transaction is temporarily banned, however it can + // become valid at a later time. + Error::TemporarilyBanned | + // The pool is full at the moment. + Error::ImmediatelyDropped | + // The block id is not known to the pool. + // The node might be lagging behind, or during a warp sync. + Error::InvalidBlockId(_) | + // The pool is configured to not accept future transactions. + Error::RejectedFutureTransaction => { + true + } + _ => false + } + } +} + /// Transaction pool error conversion. pub trait IntoPoolError: std::error::Error + Send + Sized + Sync { /// Try to extract original `Error` diff --git a/substrate/client/transaction-pool/api/src/lib.rs b/substrate/client/transaction-pool/api/src/lib.rs index a795917528f9cca0831068fc77dd346fd2390838..0a313c5b782d90f9dab5e3de326160c37cc8a45d 100644 --- a/substrate/client/transaction-pool/api/src/lib.rs +++ b/substrate/client/transaction-pool/api/src/lib.rs @@ -62,20 +62,27 @@ impl PoolStatus { /// /// The status events can be grouped based on their kinds as: /// 1. Entering/Moving within the pool: -/// - `Future` -/// - `Ready` +/// - [Future](TransactionStatus::Future) +/// - [Ready](TransactionStatus::Ready) /// 2. Inside `Ready` queue: -/// - `Broadcast` +/// - [Broadcast](TransactionStatus::Broadcast) /// 3. Leaving the pool: -/// - `InBlock` -/// - `Invalid` -/// - `Usurped` -/// - `Dropped` +/// - [InBlock](TransactionStatus::InBlock) +/// - [Invalid](TransactionStatus::Invalid) +/// - [Usurped](TransactionStatus::Usurped) +/// - [Dropped](TransactionStatus::Dropped) /// 4. Re-entering the pool: -/// - `Retracted` +/// - [Retracted](TransactionStatus::Retracted) /// 5. Block finalized: -/// - `Finalized` -/// - `FinalityTimeout` +/// - [Finalized](TransactionStatus::Finalized) +/// - [FinalityTimeout](TransactionStatus::FinalityTimeout) +/// +/// Transactions are first placed in either the `Ready` or `Future` queues of the transaction pool. +/// Substrate validates the transaction before it enters the pool. +/// +/// A transaction is placed in the `Future` queue if it will become valid at a future time. +/// For example, submitting a transaction with a higher account nonce than the current +/// expected nonce will place the transaction in the `Future` queue. /// /// The events will always be received in the order described above, however /// there might be cases where transactions alternate between `Future` and `Ready` @@ -88,19 +95,37 @@ impl PoolStatus { /// 1. Due to possible forks, the transaction that ends up being in included /// in one block, may later re-enter the pool or be marked as invalid. /// 2. Transaction `Dropped` at one point, may later re-enter the pool if some other -/// transactions are removed. +/// transactions are removed. A `Dropped` transaction may re-enter the pool only if it is +/// resubmitted. /// 3. `Invalid` transaction may become valid at some point in the future. /// (Note that runtimes are encouraged to use `UnknownValidity` to inform the pool about -/// such case). +/// such case). An `Invalid` transaction may re-enter the pool only if it is resubmitted. /// 4. `Retracted` transactions might be included in some next block. /// -/// The stream is considered finished only when either `Finalized` or `FinalityTimeout` -/// event is triggered. You are however free to unsubscribe from notifications at any point. -/// The first one will be emitted when the block, in which transaction was included gets -/// finalized. The `FinalityTimeout` event will be emitted when the block did not reach finality +/// The `FinalityTimeout` event will be emitted when the block did not reach finality /// within 512 blocks. This either indicates that finality is not available for your chain, /// or that finality gadget is lagging behind. If you choose to wait for finality longer, you can /// re-subscribe for a particular transaction hash manually again. +/// +/// ### Last Event +/// +/// The stream is considered finished when one of the following events happen: +/// - [Finalized](TransactionStatus::Finalized) +/// - [FinalityTimeout](TransactionStatus::FinalityTimeout) +/// - [Usurped](TransactionStatus::Usurped) +/// - [Invalid](TransactionStatus::Invalid) +/// - [Dropped](TransactionStatus::Dropped) +/// +/// See [`TransactionStatus::is_final`] for more details. +/// +/// ### Resubmit Transactions +/// +/// Users might resubmit the transaction at a later time for the following events: +/// - [FinalityTimeout](TransactionStatus::FinalityTimeout) +/// - [Invalid](TransactionStatus::Invalid) +/// - [Dropped](TransactionStatus::Dropped) +/// +/// See [`TransactionStatus::is_retriable`] for more details. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TransactionStatus { @@ -131,6 +156,38 @@ pub enum TransactionStatus { Invalid, } +impl TransactionStatus { + /// Returns true if this is the last event emitted by [`TransactionStatusStream`]. + pub fn is_final(&self) -> bool { + // The state must be kept in sync with `crate::graph::Sender`. + match self { + Self::Usurped(_) | + Self::Finalized(_) | + Self::FinalityTimeout(_) | + Self::Invalid | + Self::Dropped => true, + _ => false, + } + } + + /// Returns true if the transaction could be re-submitted to the pool in the future. + /// + /// For example, `TransactionStatus::Dropped` is retriable, because the transaction + /// may enter the pool if there is space for it in the future. + pub fn is_retriable(&self) -> bool { + match self { + // The number of finality watchers has been reached. + Self::FinalityTimeout(_) | + // An invalid transaction might be valid at a later time. + Self::Invalid | + // The transaction was dropped because of the limits of the pool. + // It can reenter the pool when other transactions are removed / finalized. + Self::Dropped => true, + _ => false, + } + } +} + /// The stream of transaction events. pub type TransactionStatusStream = dyn Stream> + Send; diff --git a/substrate/client/utils/Cargo.toml b/substrate/client/utils/Cargo.toml index ec7b3d0eb0dcbbb23e5512b4d3cf582a4eb25e54..7f604219bc09a96ebb8114df01e10f7d474a87f4 100644 --- a/substrate/client/utils/Cargo.toml +++ b/substrate/client/utils/Cargo.toml @@ -17,7 +17,7 @@ async-channel = "1.8.0" futures = "0.3.21" futures-timer = "3.0.2" lazy_static = "1.4.0" -log = "0.4" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" prometheus = { version = "0.13.0", default-features = false } sp-arithmetic = { path = "../../primitives/arithmetic", default-features = false } diff --git a/substrate/frame/Cargo.toml b/substrate/frame/Cargo.toml index 9419eb15974b368cc706e9b7092f50e86149bb80..3f148bf4c83bfcbcf1892bdb0ebb83abb70b9cce 100644 --- a/substrate/frame/Cargo.toml +++ b/substrate/frame/Cargo.toml @@ -48,8 +48,7 @@ frame-executive = { default-features = false, path = "../frame/executive", optio frame-system-rpc-runtime-api = { default-features = false, path = "../frame/system/rpc/runtime-api", optional = true } docify = "0.2.7" -simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", rev = "e48b187bcfd5cc75111acd9d241f1bd36604344b", optional = true } -log = { version = "0.4.20", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-examples = { path = "./examples" } @@ -78,7 +77,6 @@ std = [ "log/std", "parity-scale-codec/std", "scale-info/std", - "simple-mermaid", "sp-api?/std", "sp-arithmetic/std", "sp-block-builder?/std", diff --git a/substrate/frame/alliance/Cargo.toml b/substrate/frame/alliance/Cargo.toml index 955f9e268c6f1e80c778a596d171f74260a08c8e..bc873ad69c803a9f686d5329cffde11c76e8dc35 100644 --- a/substrate/frame/alliance/Cargo.toml +++ b/substrate/frame/alliance/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { version = "6.1", optional = true } -log = { version = "0.4.14", default-features = false } +log = { workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } diff --git a/substrate/frame/alliance/src/benchmarking.rs b/substrate/frame/alliance/src/benchmarking.rs index b69d0156ec4b04952e15c290a7765db553fab5c8..9fe0e29b42cd9526a1e221531dd5946846b17b90 100644 --- a/substrate/frame/alliance/src/benchmarking.rs +++ b/substrate/frame/alliance/src/benchmarking.rs @@ -19,13 +19,12 @@ #![cfg(feature = "runtime-benchmarks")] -use sp_runtime::traits::{Bounded, Hash, StaticLookup}; -use sp_std::{ +use core::{ cmp, convert::{TryFrom, TryInto}, mem::size_of, - prelude::*, }; +use sp_runtime::traits::{Bounded, Hash, StaticLookup}; use frame_benchmarking::{account, impl_benchmark_test_suite, v2::*, BenchmarkError}; use frame_support::traits::{EnsureOrigin, Get, UnfilteredDispatchable}; diff --git a/substrate/frame/alliance/src/mock.rs b/substrate/frame/alliance/src/mock.rs index 627dde81afa84926b719803466fd5c5e14d9bc47..4a65485ed8f6643e2ab38e49150eb3901ded7559 100644 --- a/substrate/frame/alliance/src/mock.rs +++ b/substrate/frame/alliance/src/mock.rs @@ -17,13 +17,13 @@ //! Test utilities +use core::convert::{TryFrom, TryInto}; pub use sp_core::H256; use sp_runtime::traits::Hash; pub use sp_runtime::{ traits::{BlakeTwo256, IdentifyAccount, Lazy, Verify}, BuildStorage, }; -use sp_std::convert::{TryFrom, TryInto}; pub use frame_support::{ assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, diff --git a/substrate/frame/asset-conversion/src/lib.rs b/substrate/frame/asset-conversion/src/lib.rs index f0695678fbddf07a4943c4ad0982ac95c4634866..f13d40d3e7e2c74edc805ed2d4cb8154bb507480 100644 --- a/substrate/frame/asset-conversion/src/lib.rs +++ b/substrate/frame/asset-conversion/src/lib.rs @@ -40,7 +40,7 @@ //! non-native asset 1, you would pass in a path of `[DOT, 1]` or `[1, DOT]`. If you want to swap //! from non-native asset 1 to non-native asset 2, you would pass in a path of `[1, DOT, 2]`. //! -//! (For an example of configuring this pallet to use `MultiLocation` as an asset id, see the +//! (For an example of configuring this pallet to use `Location` as an asset id, see the //! cumulus repo). //! //! Here is an example `state_call` that asks for a quote of a pool of native versus asset 1: diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index dd1d26ff238deeac52d2c8089706bbc688586f14..870538a68cc7105802672972bdf394e36f7bbc87 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -19,6 +19,7 @@ use super::*; use crate as pallet_asset_conversion; +use core::default::Default; use frame_support::{ construct_runtime, derive_impl, instances::{Instance1, Instance2}, @@ -28,18 +29,16 @@ use frame_support::{ fungible::{NativeFromLeft, NativeOrWithId, UnionOf}, imbalance::ResolveAssetTo, }, - AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64, + AsEnsureOriginWithArg, ConstU128, ConstU32, }, PalletId, }; use frame_system::{EnsureSigned, EnsureSignedBy}; use sp_arithmetic::Permill; -use sp_core::H256; use sp_runtime::{ - traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, + traits::{AccountIdConversion, IdentityLookup}, BuildStorage, }; -use sp_std::default::Default; type Block = frame_system::mocking::MockBlock; @@ -56,29 +55,10 @@ construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/asset-conversion/src/types.rs b/substrate/frame/asset-conversion/src/types.rs index fd6d41a55b6139b74453861fd553f0d30d307146..5ee81c2012de226baf6aafbda0e2ec9a380cc1e2 100644 --- a/substrate/frame/asset-conversion/src/types.rs +++ b/substrate/frame/asset-conversion/src/types.rs @@ -17,8 +17,8 @@ use super::*; use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; use scale_info::TypeInfo; -use sp_std::marker::PhantomData; /// Represents a swap path with associated asset amounts indicating how much of the asset needs to /// be deposited to get the following asset's amount withdrawn (this is inclusive of fees). diff --git a/substrate/frame/asset-rate/src/lib.rs b/substrate/frame/asset-rate/src/lib.rs index d4afca8b73c4b2978bbed954c05aee4e2a645ee5..befabfe54aa0e766d498eccf2201de4e9c0f2fc9 100644 --- a/substrate/frame/asset-rate/src/lib.rs +++ b/substrate/frame/asset-rate/src/lib.rs @@ -59,8 +59,14 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{fungible::Inspect, tokens::ConversionFromAssetBalance}; -use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128}; +use frame_support::traits::{ + fungible::Inspect, + tokens::{ConversionFromAssetBalance, ConversionToAssetBalance}, +}; +use sp_runtime::{ + traits::{CheckedDiv, Zero}, + FixedPointNumber, FixedU128, +}; use sp_std::boxed::Box; pub use pallet::*; @@ -144,6 +150,8 @@ pub mod pallet { UnknownAssetKind, /// The given asset ID already has an assigned conversion rate and cannot be re-created. AlreadyExists, + /// Overflow ocurred when calculating the inverse rate. + Overflow, } #[pallet::call] @@ -246,3 +254,25 @@ where pallet::ConversionRateToNative::::set(asset_id.clone(), Some(1.into())); } } + +/// Exposes conversion of a native balance to an asset balance. +impl ConversionToAssetBalance, AssetKindOf, BalanceOf> for Pallet +where + T: Config, +{ + type Error = pallet::Error; + + fn to_asset_balance( + balance: BalanceOf, + asset_kind: AssetKindOf, + ) -> Result, pallet::Error> { + let rate = pallet::ConversionRateToNative::::get(asset_kind) + .ok_or(pallet::Error::::UnknownAssetKind.into())?; + + // We cannot use `saturating_div` here so we use `checked_div`. + Ok(FixedU128::from_u32(1) + .checked_div(&rate) + .ok_or(pallet::Error::::Overflow.into())? + .saturating_mul_int(balance)) + } +} diff --git a/substrate/frame/asset-rate/src/mock.rs b/substrate/frame/asset-rate/src/mock.rs index d6044e09ccd1c821b43bd54708f89cb5fd1e1ac9..5981b05676414e9ec5251be0bcf2e554d589107a 100644 --- a/substrate/frame/asset-rate/src/mock.rs +++ b/substrate/frame/asset-rate/src/mock.rs @@ -18,15 +18,8 @@ //! The crate's mock. use crate as pallet_asset_rate; -use frame_support::{ - derive_impl, - traits::{ConstU16, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use frame_support::{derive_impl, traits::ConstU64}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -41,29 +34,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = ConstU16<42>; - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/asset-rate/src/tests.rs b/substrate/frame/asset-rate/src/tests.rs index 452265476093b034256907e9d2a232c91041f772..fc7bf2f150311b638ba3a7bc939d6b62ba927cc0 100644 --- a/substrate/frame/asset-rate/src/tests.rs +++ b/substrate/frame/asset-rate/src/tests.rs @@ -131,12 +131,19 @@ fn convert_works() { FixedU128::from_float(2.51) )); - let conversion = , ::AssetKind, BalanceOf, >>::from_asset_balance(10, ASSET_ID); - assert_eq!(conversion.expect("Conversion rate exists for asset"), 25); + assert_eq!(conversion_from_asset.expect("Conversion rate exists for asset"), 25); + + let conversion_to_asset = , + ::AssetKind, + BalanceOf, + >>::to_asset_balance(25, ASSET_ID); + assert_eq!(conversion_to_asset.expect("Conversion rate exists for asset"), 9); }); } @@ -151,3 +158,21 @@ fn convert_unknown_throws() { assert!(conversion.is_err()); }); } + +#[test] +fn convert_overflow_throws() { + new_test_ext().execute_with(|| { + assert_ok!(AssetRate::create( + RuntimeOrigin::root(), + Box::new(ASSET_ID), + FixedU128::from_u32(0) + )); + + let conversion = , + ::AssetKind, + BalanceOf, + >>::to_asset_balance(10, ASSET_ID); + assert!(conversion.is_err()); + }); +} diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index 6fe3a27236052d1139327a549f9276caa6730c3b..2efc96348cb5447b792c334256076a395f646719 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } sp-std = { path = "../../primitives/std", default-features = false } # Needed for various traits. In our case, `OnFinalize`. diff --git a/substrate/frame/assets/src/extra_mutator.rs b/substrate/frame/assets/src/extra_mutator.rs index 2a44df5f0c661ffc83b0f9c0df68d14bcfa0bcab..e2f7742c694a3caf94e70e596f667bb7759bb2bb 100644 --- a/substrate/frame/assets/src/extra_mutator.rs +++ b/substrate/frame/assets/src/extra_mutator.rs @@ -38,7 +38,7 @@ impl, I: 'static> Drop for ExtraMutator { } } -impl, I: 'static> sp_std::ops::Deref for ExtraMutator { +impl, I: 'static> core::ops::Deref for ExtraMutator { type Target = T::Extra; fn deref(&self) -> &T::Extra { match self.pending { @@ -48,7 +48,7 @@ impl, I: 'static> sp_std::ops::Deref for ExtraMutator { } } -impl, I: 'static> sp_std::ops::DerefMut for ExtraMutator { +impl, I: 'static> core::ops::DerefMut for ExtraMutator { fn deref_mut(&mut self) -> &mut T::Extra { if self.pending.is_none() { self.pending = Some(self.original.clone()); @@ -60,7 +60,7 @@ impl, I: 'static> sp_std::ops::DerefMut for ExtraMutator { impl, I: 'static> ExtraMutator { pub(super) fn maybe_new( id: T::AssetId, - who: impl sp_std::borrow::Borrow, + who: impl core::borrow::Borrow, ) -> Option> { if let Some(a) = Account::::get(&id, who.borrow()) { Some(ExtraMutator:: { diff --git a/substrate/frame/assets/src/migration.rs b/substrate/frame/assets/src/migration.rs index efe77714c524d38e9dd822652e39762848fe5367..ff0ffbff0d362fbd630e22dcb293f28e2b75469c 100644 --- a/substrate/frame/assets/src/migration.rs +++ b/substrate/frame/assets/src/migration.rs @@ -64,7 +64,7 @@ pub mod v1 { } } - pub struct MigrateToV1(sp_std::marker::PhantomData); + pub struct MigrateToV1(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { fn on_runtime_upgrade() -> Weight { let current_version = Pallet::::current_storage_version(); diff --git a/substrate/frame/assets/src/mock.rs b/substrate/frame/assets/src/mock.rs index 6dda08eaff8bb781fb7f29fc514f1672784de372..e1722200c35d4c3674db8a56dbee092f1f7ff8a8 100644 --- a/substrate/frame/assets/src/mock.rs +++ b/substrate/frame/assets/src/mock.rs @@ -25,12 +25,8 @@ use frame_support::{ construct_runtime, derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; -use sp_core::H256; use sp_io::storage; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -48,28 +44,8 @@ type AssetId = u32; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); type MaxConsumers = ConstU32<3>; } diff --git a/substrate/frame/assets/src/types.rs b/substrate/frame/assets/src/types.rs index 67f9bf07f5e7e2dfe1edc1f0a8236d06cd56ef61..11edc7d3fcb585773bc2d49349948fd56299e7d3 100644 --- a/substrate/frame/assets/src/types.rs +++ b/substrate/frame/assets/src/types.rs @@ -123,7 +123,7 @@ where return None } if let ExistenceReason::DepositHeld(deposit) = - sp_std::mem::replace(self, ExistenceReason::DepositRefunded) + core::mem::replace(self, ExistenceReason::DepositRefunded) { Some(deposit) } else { @@ -136,7 +136,7 @@ where return None } if let ExistenceReason::DepositFrom(depositor, deposit) = - sp_std::mem::replace(self, ExistenceReason::DepositRefunded) + core::mem::replace(self, ExistenceReason::DepositRefunded) { Some((depositor, deposit)) } else { diff --git a/substrate/frame/atomic-swap/src/lib.rs b/substrate/frame/atomic-swap/src/lib.rs index 8094c06030120c9a7583aed3f64dc2a5de7260d4..609903e67e3e56f7016a20772a996a41705839d7 100644 --- a/substrate/frame/atomic-swap/src/lib.rs +++ b/substrate/frame/atomic-swap/src/lib.rs @@ -43,6 +43,10 @@ mod tests; use codec::{Decode, Encode}; +use core::{ + marker::PhantomData, + ops::{Deref, DerefMut}, +}; use frame_support::{ dispatch::DispatchResult, pallet_prelude::MaxEncodedLen, @@ -54,11 +58,6 @@ use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::RuntimeDebug; -use sp_std::{ - marker::PhantomData, - ops::{Deref, DerefMut}, - prelude::*, -}; /// Pending atomic swap operation. #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] diff --git a/substrate/frame/atomic-swap/src/tests.rs b/substrate/frame/atomic-swap/src/tests.rs index 4a4b96f7aae05a874bc662848135fad1380e49c3..4b444d888ed5e228a4951258092b0bdbdcf6e597 100644 --- a/substrate/frame/atomic-swap/src/tests.rs +++ b/substrate/frame/atomic-swap/src/tests.rs @@ -24,11 +24,7 @@ use frame_support::{ derive_impl, traits::{ConstU32, ConstU64}, }; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -43,29 +39,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/aura/Cargo.toml b/substrate/frame/aura/Cargo.toml index 7620d172ffc98934b066558a7399d4ff3a163985..de698487efa7fa6c1cc37cc89e872b9603a9f253 100644 --- a/substrate/frame/aura/Cargo.toml +++ b/substrate/frame/aura/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/aura/src/migrations.rs b/substrate/frame/aura/src/migrations.rs index b45e4eb7cb5c866920a72601b5db4baa22d16d87..727fe281aa38430a5cd246d0dfba098c36d578e1 100644 --- a/substrate/frame/aura/src/migrations.rs +++ b/substrate/frame/aura/src/migrations.rs @@ -19,7 +19,7 @@ use frame_support::{pallet_prelude::*, traits::Get, weights::Weight}; -struct __LastTimestamp(sp_std::marker::PhantomData); +struct __LastTimestamp(core::marker::PhantomData); impl frame_support::traits::StorageInstance for __LastTimestamp { fn pallet_prefix() -> &'static str { T::PalletPrefix::get() diff --git a/substrate/frame/aura/src/mock.rs b/substrate/frame/aura/src/mock.rs index d87fea8fc81ebcc9ccb2fb03875a4c45959b7057..8bc3e407158317164730e839bf954fd5f8e5e597 100644 --- a/substrate/frame/aura/src/mock.rs +++ b/substrate/frame/aura/src/mock.rs @@ -25,8 +25,7 @@ use frame_support::{ traits::{ConstU32, ConstU64, DisabledValidators}, }; use sp_consensus_aura::{ed25519::AuthorityId, AuthorityIndex}; -use sp_core::H256; -use sp_runtime::{testing::UintAuthorityId, traits::IdentityLookup, BuildStorage}; +use sp_runtime::{testing::UintAuthorityId, BuildStorage}; type Block = frame_system::mocking::MockBlock; @@ -43,29 +42,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_timestamp::Config for Test { diff --git a/substrate/frame/authority-discovery/src/lib.rs b/substrate/frame/authority-discovery/src/lib.rs index 640c0c8759ebef6c390e9119fb1b96361bc34e25..2b4dfaf1aea8c804af7b4893be7057c53c347896 100644 --- a/substrate/frame/authority-discovery/src/lib.rs +++ b/substrate/frame/authority-discovery/src/lib.rs @@ -168,13 +168,10 @@ impl OneSessionHandler for Pallet { mod tests { use super::*; use crate as pallet_authority_discovery; - use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, - }; + use frame_support::{derive_impl, parameter_types, traits::ConstU32}; use sp_application_crypto::Pair; use sp_authority_discovery::AuthorityPair; - use sp_core::{crypto::key_types, H256}; + use sp_core::crypto::key_types; use sp_io::TestExternalities; use sp_runtime::{ testing::UintAuthorityId, @@ -227,29 +224,9 @@ mod tests { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AuthorityId; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } pub struct TestSessionHandler; diff --git a/substrate/frame/babe/Cargo.toml b/substrate/frame/babe/Cargo.toml index 8f49faaa2d602bb9c8f8894e45f28d481adf2670..fc7385efa1f14c371af682304f3e1c8d3c614c2d 100644 --- a/substrate/frame/babe/Cargo.toml +++ b/substrate/frame/babe/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/babe/src/lib.rs b/substrate/frame/babe/src/lib.rs index a6e44390dbc534e15e3a1658513c6ce9fa25088e..5fb107dde3bad06e6f4812d27ff1159a95194442 100644 --- a/substrate/frame/babe/src/lib.rs +++ b/substrate/frame/babe/src/lib.rs @@ -323,7 +323,7 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig { pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, - pub epoch_config: Option, + pub epoch_config: BabeEpochConfiguration, #[serde(skip)] pub _config: sp_std::marker::PhantomData, } @@ -333,9 +333,7 @@ pub mod pallet { fn build(&self) { SegmentIndex::::put(0); Pallet::::initialize_genesis_authorities(&self.authorities); - EpochConfig::::put( - self.epoch_config.clone().expect("epoch_config must not be None"), - ); + EpochConfig::::put(&self.epoch_config); } } diff --git a/substrate/frame/babe/src/randomness.rs b/substrate/frame/babe/src/randomness.rs index d3d1bea2292da949dd1bac3aca20f6d33cb6d258..fd52981d2a1dd46c01e5706d1f8212b621c0b82c 100644 --- a/substrate/frame/babe/src/randomness.rs +++ b/substrate/frame/babe/src/randomness.rs @@ -51,7 +51,7 @@ use sp_runtime::traits::{Hash, One, Saturating}; /// /// Adversaries should not possess many block production slots towards the beginning or /// end of every epoch, but they possess some influence over when they possess more slots. -pub struct RandomnessFromTwoEpochsAgo(sp_std::marker::PhantomData); +pub struct RandomnessFromTwoEpochsAgo(core::marker::PhantomData); /// Randomness usable by on-chain code that **does not depend** upon finality and takes /// action based upon on-chain commitments made during the previous epoch. @@ -79,7 +79,7 @@ pub struct RandomnessFromTwoEpochsAgo(sp_std::marker::PhantomData); /// As an example usage, we determine parachain auctions ending times in Polkadot using /// `RandomnessFromOneEpochAgo` because it reduces bias from `ParentBlockRandomness` and /// does not require the extra finality delay of `RandomnessFromTwoEpochsAgo`. -pub struct RandomnessFromOneEpochAgo(sp_std::marker::PhantomData); +pub struct RandomnessFromOneEpochAgo(core::marker::PhantomData); /// Randomness produced semi-freshly with each block, but inherits limitations of /// `RandomnessFromTwoEpochsAgo` from which it derives. @@ -119,7 +119,7 @@ pub struct RandomnessFromOneEpochAgo(sp_std::marker::PhantomData); /// instead you are using this randomness externally, i.e. after block execution, then /// this randomness will be provided by the "current" block (this stems from the fact that /// we process VRF outputs on block execution finalization, i.e. `on_finalize`). -pub struct ParentBlockRandomness(sp_std::marker::PhantomData); +pub struct ParentBlockRandomness(core::marker::PhantomData); /// Randomness produced semi-freshly with each block, but inherits limitations of /// `RandomnessFromTwoEpochsAgo` from which it derives. @@ -128,7 +128,7 @@ pub struct ParentBlockRandomness(sp_std::marker::PhantomData); #[deprecated(note = "Should not be relied upon for correctness, \ will not provide fresh randomness for the current block. \ Please use `ParentBlockRandomness` instead.")] -pub struct CurrentBlockRandomness(sp_std::marker::PhantomData); +pub struct CurrentBlockRandomness(core::marker::PhantomData); impl RandomnessT> for RandomnessFromTwoEpochsAgo { fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor) { diff --git a/substrate/frame/bags-list/Cargo.toml b/substrate/frame/bags-list/Cargo.toml index b8ab099a0694220b933758fbd96a6635216d572f..f9ae462e16d729f9652da114f5ebcef4cc2f21b7 100644 --- a/substrate/frame/bags-list/Cargo.toml +++ b/substrate/frame/bags-list/Cargo.toml @@ -33,7 +33,7 @@ frame-system = { path = "../system", default-features = false } frame-election-provider-support = { path = "../election-provider-support", default-features = false } # third party -log = { version = "0.4.17", default-features = false } +log = { workspace = true } docify = "0.2.7" aquamarine = { version = "0.5.0" } diff --git a/substrate/frame/bags-list/remote-tests/Cargo.toml b/substrate/frame/bags-list/remote-tests/Cargo.toml index fb61a9867783a32084dc363f6ac18c76ed4f3e10..266355f5cabe19214643db024e641339acd60630 100644 --- a/substrate/frame/bags-list/remote-tests/Cargo.toml +++ b/substrate/frame/bags-list/remote-tests/Cargo.toml @@ -34,4 +34,4 @@ sp-std = { path = "../../../primitives/std" } remote-externalities = { package = "frame-remote-externalities", path = "../../../utils/frame/remote-externalities" } # others -log = "0.4.17" +log = { workspace = true, default-features = true } diff --git a/substrate/frame/balances/Cargo.toml b/substrate/frame/balances/Cargo.toml index e47e916a274de8074b58692e36df47610efb186b..64ae90c67575fc1dcd139b7ed6dcbaa1c3ffda59 100644 --- a/substrate/frame/balances/Cargo.toml +++ b/substrate/frame/balances/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/balances/src/tests/mod.rs b/substrate/frame/balances/src/tests/mod.rs index 91452b292b56f90083c0f82aa3a1b361fb3d7baf..599909fa94351b748ce7ea0514cc574347ea6a7a 100644 --- a/substrate/frame/balances/src/tests/mod.rs +++ b/substrate/frame/balances/src/tests/mod.rs @@ -26,18 +26,18 @@ use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo}, parameter_types, traits::{ - fungible, ConstU32, ConstU64, ConstU8, Imbalance as ImbalanceT, OnUnbalanced, - StorageMapShim, StoredMap, VariantCount, WhitelistedStorageKeys, + fungible, ConstU32, ConstU8, Imbalance as ImbalanceT, OnUnbalanced, StorageMapShim, + StoredMap, VariantCount, WhitelistedStorageKeys, }, weights::{IdentityFee, Weight}, }; use frame_system::{self as system, RawOrigin}; use pallet_transaction_payment::{ChargeTransactionPayment, CurrencyAdapter, Multiplier}; use scale_info::TypeInfo; -use sp_core::{hexdisplay::HexDisplay, H256}; +use sp_core::hexdisplay::HexDisplay; use sp_io; use sp_runtime::{ - traits::{BadOrigin, IdentityLookup, SignedExtension, Zero}, + traits::{BadOrigin, SignedExtension, Zero}, ArithmeticError, BuildStorage, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, TokenError, }; @@ -92,29 +92,8 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = super::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_transaction_payment::Config for Test { diff --git a/substrate/frame/beefy-mmr/Cargo.toml b/substrate/frame/beefy-mmr/Cargo.toml index 250a6fb450ca730476ecdba5bc8d5e81a0490b62..17707731773118b7028f3cff8031982c0c5d704b 100644 --- a/substrate/frame/beefy-mmr/Cargo.toml +++ b/substrate/frame/beefy-mmr/Cargo.toml @@ -14,9 +14,9 @@ workspace = true [dependencies] array-bytes = { version = "6.1", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } binary-merkle-tree = { path = "../../utils/binary-merkle-tree", default-features = false } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/beefy/Cargo.toml b/substrate/frame/beefy/Cargo.toml index ba02cf1460d3ec32e8358169437ee0a2c93a5b39..e38eaa6fb0787aca48b07fad57e0ea0adffbe7e4 100644 --- a/substrate/frame/beefy/Cargo.toml +++ b/substrate/frame/beefy/Cargo.toml @@ -13,9 +13,9 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } pallet-authorship = { path = "../authorship", default-features = false } diff --git a/substrate/frame/beefy/src/tests.rs b/substrate/frame/beefy/src/tests.rs index bf5ae19510ce9320c7aac3b705db605db16ecebd..453cf19a4fe156a76ed13a7da4fe59d95d4260f0 100644 --- a/substrate/frame/beefy/src/tests.rs +++ b/substrate/frame/beefy/src/tests.rs @@ -15,21 +15,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::vec; - use codec::Encode; -use sp_consensus_beefy::{ - check_equivocation_proof, generate_equivocation_proof, known_payloads::MMR_ROOT_ID, - Keyring as BeefyKeyring, Payload, ValidatorSet, KEY_TYPE as BEEFY_KEY_TYPE, -}; - -use sp_runtime::DigestItem; +use std::vec; use frame_support::{ assert_err, assert_ok, dispatch::{GetDispatchInfo, Pays}, traits::{Currency, KeyOwnerProofSystem, OnInitialize}, }; +use sp_consensus_beefy::{ + check_equivocation_proof, + known_payloads::MMR_ROOT_ID, + test_utils::{generate_equivocation_proof, Keyring as BeefyKeyring}, + Payload, ValidatorSet, KEY_TYPE as BEEFY_KEY_TYPE, +}; +use sp_runtime::DigestItem; use crate::{mock::*, Call, Config, Error, Weight, WeightInfo}; diff --git a/substrate/frame/benchmarking/Cargo.toml b/substrate/frame/benchmarking/Cargo.toml index 38c7bc2b905d37531f97906616f490ab4d4181a1..bf42aae979cd0edd3a00ca98f8aacaba55d807dc 100644 --- a/substrate/frame/benchmarking/Cargo.toml +++ b/substrate/frame/benchmarking/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } linregress = { version = "0.5.1", optional = true } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } paste = "1.0" scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-support = { path = "../support", default-features = false } frame-support-procedural = { path = "../support/procedural", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/bounties/Cargo.toml b/substrate/frame/bounties/Cargo.toml index df7c3c0cbe56098a03a87e462f71da07f4432f8f..191a38d20b2faf4a923bc074afd905c523c8a2c1 100644 --- a/substrate/frame/bounties/Cargo.toml +++ b/substrate/frame/bounties/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/bounties/src/migrations/v4.rs b/substrate/frame/bounties/src/migrations/v4.rs index 4e6ba934481628f8aeca3647711cec8a7ab0fd45..71cd4f76be6e2702a1b91a390faef3111fb1a46a 100644 --- a/substrate/frame/bounties/src/migrations/v4.rs +++ b/substrate/frame/bounties/src/migrations/v4.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::str; use frame_support::{ storage::{generator::StorageValue, StoragePrefixedMap}, traits::{ @@ -25,7 +26,6 @@ use frame_support::{ }; use sp_core::hexdisplay::HexDisplay; use sp_io::{hashing::twox_128, storage}; -use sp_std::str; use crate as pallet_bounties; diff --git a/substrate/frame/bounties/src/tests.rs b/substrate/frame/bounties/src/tests.rs index b0e3c085e65df02413f121fc4c819c823822cfab..da6596617dac4cc893cf27dfd9806436822cf516 100644 --- a/substrate/frame/bounties/src/tests.rs +++ b/substrate/frame/bounties/src/tests.rs @@ -31,9 +31,8 @@ use frame_support::{ PalletId, }; -use sp_core::H256; use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + traits::{BadOrigin, IdentityLookup}, BuildStorage, Perbill, Storage, }; @@ -61,29 +60,10 @@ type Balance = u64; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/broker/src/coretime_interface.rs b/substrate/frame/broker/src/coretime_interface.rs index 9e853e8f3fe0b1e2f76e3fac57b9ce0b8d03ab07..58efa7fa92bb0fa6c29a45375593f2246f5eaa8a 100644 --- a/substrate/frame/broker/src/coretime_interface.rs +++ b/substrate/frame/broker/src/coretime_interface.rs @@ -51,7 +51,7 @@ pub enum CoreAssignment { pub type RCBlockNumberOf = as BlockNumberProvider>::BlockNumber; /// Relay chain block number provider of `T` that implements [`CoretimeInterface`]. -pub type RCBlockNumberProviderOf = ::RealyChainBlockNumberProvider; +pub type RCBlockNumberProviderOf = ::RelayChainBlockNumberProvider; /// Type able to accept Coretime scheduling instructions and provide certain usage information. /// Generally implemented by the Relay-chain or some means of communicating with it. @@ -65,7 +65,7 @@ pub trait CoretimeInterface { type Balance: AtLeast32BitUnsigned; /// A provider for the relay chain block number. - type RealyChainBlockNumberProvider: BlockNumberProvider; + type RelayChainBlockNumberProvider: BlockNumberProvider; /// Requests the Relay-chain to alter the number of schedulable cores to `count`. Under normal /// operation, the Relay-chain SHOULD send a `notify_core_count(count)` message back. @@ -128,7 +128,7 @@ pub trait CoretimeInterface { impl CoretimeInterface for () { type AccountId = (); type Balance = u64; - type RealyChainBlockNumberProvider = (); + type RelayChainBlockNumberProvider = (); fn request_core_count(_count: CoreIndex) {} fn request_revenue_info_at(_when: RCBlockNumberOf) {} diff --git a/substrate/frame/broker/src/mock.rs b/substrate/frame/broker/src/mock.rs index 19c72340353c66d9bedf5d016e02dad7102116cb..ac327c4143e7b51e5350a0065ab861b1618ca0f5 100644 --- a/substrate/frame/broker/src/mock.rs +++ b/substrate/frame/broker/src/mock.rs @@ -77,7 +77,7 @@ pub struct TestCoretimeProvider; impl CoretimeInterface for TestCoretimeProvider { type AccountId = u64; type Balance = u64; - type RealyChainBlockNumberProvider = System; + type RelayChainBlockNumberProvider = System; fn request_core_count(count: CoreIndex) { CoreCountInbox::::put(count); } diff --git a/substrate/frame/broker/src/tests.rs b/substrate/frame/broker/src/tests.rs index e5efb70ae8d5a9a3ef4370592d2c2f1f461be228..3e1e36f7d4489c2ad6c7e1e555f1c8a40250083a 100644 --- a/substrate/frame/broker/src/tests.rs +++ b/substrate/frame/broker/src/tests.rs @@ -863,6 +863,29 @@ fn cannot_set_expired_lease() { }); } +#[test] +fn short_leases_are_cleaned() { + TestExt::new().region_length(3).execute_with(|| { + assert_ok!(Broker::do_start_sales(200, 1)); + advance_to(2); + + // New leases are allowed to expire within this region given expiry > `current_timeslice`. + assert_noop!( + Broker::do_set_lease(1000, Broker::current_timeslice()), + Error::::AlreadyExpired + ); + assert_eq!(Leases::::get().len(), 0); + assert_ok!(Broker::do_set_lease(1000, Broker::current_timeslice().saturating_add(1))); + assert_eq!(Leases::::get().len(), 1); + + // But are cleaned up in the next rotate_sale. + let config = Configuration::::get().unwrap(); + let timeslice_period: u64 = ::TimeslicePeriod::get(); + advance_to(timeslice_period.saturating_mul(config.region_length.into())); + assert_eq!(Leases::::get().len(), 0); + }); +} + #[test] fn leases_are_limited() { TestExt::new().execute_with(|| { diff --git a/substrate/frame/broker/src/tick_impls.rs b/substrate/frame/broker/src/tick_impls.rs index 8b7860c8e3af6daaaa433d9b7d093c94f292a5a6..388370bce4d4b8045122c02ad758b62b4476fd15 100644 --- a/substrate/frame/broker/src/tick_impls.rs +++ b/substrate/frame/broker/src/tick_impls.rs @@ -216,7 +216,9 @@ impl Pallet { let assignment = CoreAssignment::Task(task); let schedule = BoundedVec::truncate_from(vec![ScheduleItem { mask, assignment }]); Workplan::::insert((region_begin, first_core), &schedule); - let expiring = until >= region_begin && until < region_end; + // Separate these to avoid missed expired leases hanging around forever. + let expired = until < region_end; + let expiring = until >= region_begin && expired; if expiring { // last time for this one - make it renewable. let renewal_id = AllowedRenewalId { core: first_core, when: region_end }; @@ -231,7 +233,7 @@ impl Pallet { Self::deposit_event(Event::LeaseEnding { when: region_end, task }); } first_core.saturating_inc(); - !expiring + !expired }); Leases::::put(&leases); diff --git a/substrate/frame/child-bounties/Cargo.toml b/substrate/frame/child-bounties/Cargo.toml index bcd8426c31645114230c03ca17fce6d67469c521..589ca95a7516148531434fc641af40f0f36796bb 100644 --- a/substrate/frame/child-bounties/Cargo.toml +++ b/substrate/frame/child-bounties/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/child-bounties/src/tests.rs b/substrate/frame/child-bounties/src/tests.rs index d663b8d9961de87d14a202b393a9a4259f5bcdba..276a90a3e29cf78c91e0dc141d57e90c41df7d20 100644 --- a/substrate/frame/child-bounties/src/tests.rs +++ b/substrate/frame/child-bounties/src/tests.rs @@ -32,9 +32,8 @@ use frame_support::{ PalletId, }; -use sp_core::H256; use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + traits::{BadOrigin, IdentityLookup}, BuildStorage, Perbill, Permill, TokenError, }; @@ -64,29 +63,10 @@ type Balance = u64; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/collective/Cargo.toml b/substrate/frame/collective/Cargo.toml index 91bb36bb89ec79fa8a71b0a4a41888383aab5553..e19e1496e7b5016819b1efaede408b19ae013c48 100644 --- a/substrate/frame/collective/Cargo.toml +++ b/substrate/frame/collective/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/collective/src/benchmarking.rs b/substrate/frame/collective/src/benchmarking.rs index 503d72510530903ba1676fe6630c326ef1a4f770..af10eae5b673d53b03298b3e0cba48c885dec69b 100644 --- a/substrate/frame/collective/src/benchmarking.rs +++ b/substrate/frame/collective/src/benchmarking.rs @@ -20,8 +20,8 @@ use super::*; use crate::Pallet as Collective; +use core::mem::size_of; use sp_runtime::traits::Bounded; -use sp_std::mem::size_of; use frame_benchmarking::v1::{account, benchmarks_instance_pallet, whitelisted_caller}; use frame_system::{ diff --git a/substrate/frame/collective/src/lib.rs b/substrate/frame/collective/src/lib.rs index 10f989e5c4cc6ef92c258c21455d8f227dcf3dbe..c084784e0a9bca9914cbbf879c746bfdda1774fb 100644 --- a/substrate/frame/collective/src/lib.rs +++ b/substrate/frame/collective/src/lib.rs @@ -40,7 +40,6 @@ //! If there are not, or if no prime is set, then the motion is dropped without being executed. #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "128"] use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/substrate/frame/collective/src/tests.rs b/substrate/frame/collective/src/tests.rs index dbc3fbe69b3bf27b2151963979fc1d571d9bcc7c..aae17b7ffc27bb6d9e200dee18a44c7ec0aa7dcc 100644 --- a/substrate/frame/collective/src/tests.rs +++ b/substrate/frame/collective/src/tests.rs @@ -26,11 +26,7 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EventRecord, Phase}; use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::{testing::Header, traits::BlakeTwo256, BuildStorage}; pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -93,29 +89,7 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl Config for Test { type RuntimeOrigin = RuntimeOrigin; diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index de49983a4b3fa89309b939d03e76aa053c1fd16d..be3bafcd23fa107b12005ebe45da874e2a55e16b 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -24,8 +24,8 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "max-encoded-len", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4", default-features = false } -serde = { version = "1", optional = true, features = ["derive"] } +log = { workspace = true } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } smallvec = { version = "1", default-features = false, features = [ "const_generics", ] } diff --git a/substrate/frame/contracts/benchmarks/README.md b/substrate/frame/contracts/benchmarks/README.md deleted file mode 100644 index e4441d6bab2c425a37639830f12ca303db30d1fa..0000000000000000000000000000000000000000 --- a/substrate/frame/contracts/benchmarks/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Benchmarks - -This directory contains real world ([ink!](https://use.ink), [solang](https://github.com/hyperledger/solang)) contracts -which are used in macro benchmarks. Those benchmarks are not used to determine weights but rather to compare different -contract languages and execution engines with larger wasm modules. - -Files in this directory are used by `#[extra]` benchmarks in `src/benchmarking`. The json files are for informational -purposes only and are not consumed by the benchmarks. diff --git a/substrate/frame/contracts/benchmarks/ink_erc20.json b/substrate/frame/contracts/benchmarks/ink_erc20.json deleted file mode 100644 index 390dd9b06cd4cd3ee57f83f64c9089508c68464d..0000000000000000000000000000000000000000 --- a/substrate/frame/contracts/benchmarks/ink_erc20.json +++ /dev/null @@ -1,819 +0,0 @@ -{ - "metadataVersion": "0.1.0", - "source": { - "hash": "0x6be8492017fe96b7a92bb39b4ede04b96effb8fcaf9237bfdccef7d9e732c760", - "language": "ink! 3.0.0-rc4", - "compiler": "rustc 1.56.0-nightly" - }, - "contract": { - "name": "erc20", - "version": "3.0.0-rc4", - "authors": [ - "Parity Technologies " - ] - }, - "spec": { - "constructors": [ - { - "args": [ - { - "name": "initial_supply", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - "Creates a new ERC-20 contract with the specified initial supply." - ], - "name": [ - "new" - ], - "selector": "0x9bae9d5e" - } - ], - "docs": [], - "events": [ - { - "args": [ - { - "docs": [], - "indexed": true, - "name": "from", - "type": { - "displayName": [ - "Option" - ], - "type": 15 - } - }, - { - "docs": [], - "indexed": true, - "name": "to", - "type": { - "displayName": [ - "Option" - ], - "type": 15 - } - }, - { - "docs": [], - "indexed": false, - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Event emitted when a token transfer occurs." - ], - "name": "Transfer" - }, - { - "args": [ - { - "docs": [], - "indexed": true, - "name": "owner", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "docs": [], - "indexed": true, - "name": "spender", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "docs": [], - "indexed": false, - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Event emitted when an approval occurs that `spender` is allowed to withdraw", - " up to the amount of `value` tokens from `owner`." - ], - "name": "Approval" - } - ], - "messages": [ - { - "args": [], - "docs": [ - " Returns the total token supply." - ], - "mutates": false, - "name": [ - "total_supply" - ], - "payable": false, - "returnType": { - "displayName": [ - "Balance" - ], - "type": 1 - }, - "selector": "0xdb6375a8" - }, - { - "args": [ - { - "name": "owner", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - " Returns the account balance for the specified `owner`.", - "", - " Returns `0` if the account is non-existent." - ], - "mutates": false, - "name": [ - "balance_of" - ], - "payable": false, - "returnType": { - "displayName": [ - "Balance" - ], - "type": 1 - }, - "selector": "0x0f755a56" - }, - { - "args": [ - { - "name": "owner", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "spender", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - " Returns the amount which `spender` is still allowed to withdraw from `owner`.", - "", - " Returns `0` if no allowance has been set `0`." - ], - "mutates": false, - "name": [ - "allowance" - ], - "payable": false, - "returnType": { - "displayName": [ - "Balance" - ], - "type": 1 - }, - "selector": "0x6a00165e" - }, - { - "args": [ - { - "name": "to", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Transfers `value` amount of tokens from the caller's account to account `to`.", - "", - " On success a `Transfer` event is emitted.", - "", - " # Errors", - "", - " Returns `InsufficientBalance` error if there are not enough tokens on", - " the caller's account balance." - ], - "mutates": true, - "name": [ - "transfer" - ], - "payable": false, - "returnType": { - "displayName": [ - "Result" - ], - "type": 12 - }, - "selector": "0x84a15da1" - }, - { - "args": [ - { - "name": "spender", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Allows `spender` to withdraw from the caller's account multiple times, up to", - " the `value` amount.", - "", - " If this function is called again it overwrites the current allowance with `value`.", - "", - " An `Approval` event is emitted." - ], - "mutates": true, - "name": [ - "approve" - ], - "payable": false, - "returnType": { - "displayName": [ - "Result" - ], - "type": 12 - }, - "selector": "0x681266a0" - }, - { - "args": [ - { - "name": "from", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "to", - "type": { - "displayName": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "value", - "type": { - "displayName": [ - "Balance" - ], - "type": 1 - } - } - ], - "docs": [ - " Transfers `value` tokens on the behalf of `from` to the account `to`.", - "", - " This can be used to allow a contract to transfer tokens on ones behalf and/or", - " to charge fees in sub-currencies, for example.", - "", - " On success a `Transfer` event is emitted.", - "", - " # Errors", - "", - " Returns `InsufficientAllowance` error if there are not enough tokens allowed", - " for the caller to withdraw from `from`.", - "", - " Returns `InsufficientBalance` error if there are not enough tokens on", - " the account balance of `from`." - ], - "mutates": true, - "name": [ - "transfer_from" - ], - "payable": false, - "returnType": { - "displayName": [ - "Result" - ], - "type": 12 - }, - "selector": "0x0b396f18" - } - ] - }, - "storage": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000000", - "ty": 1 - } - }, - "name": "total_supply" - }, - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0100000000000000000000000000000000000000000000000000000000000000", - "ty": 2 - } - }, - "name": "header" - }, - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0200000000000000000000000000000000000000000000000000000000000000", - "ty": 3 - } - }, - "name": "len" - }, - { - "layout": { - "array": { - "cellsPerElem": 1, - "layout": { - "cell": { - "key": "0x0200000001000000000000000000000000000000000000000000000000000000", - "ty": 4 - } - }, - "len": 4294967295, - "offset": "0x0300000000000000000000000000000000000000000000000000000000000000" - } - }, - "name": "elems" - } - ] - } - }, - "name": "entries" - } - ] - } - }, - "name": "keys" - }, - { - "layout": { - "hash": { - "layout": { - "cell": { - "key": "0x0300000001000000000000000000000000000000000000000000000000000000", - "ty": 9 - } - }, - "offset": "0x0200000001000000000000000000000000000000000000000000000000000000", - "strategy": { - "hasher": "Blake2x256", - "postfix": "", - "prefix": "0x696e6b20686173686d6170" - } - } - }, - "name": "values" - } - ] - } - }, - "name": "balances" - }, - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0300000001000000000000000000000000000000000000000000000000000000", - "ty": 2 - } - }, - "name": "header" - }, - { - "layout": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0400000001000000000000000000000000000000000000000000000000000000", - "ty": 3 - } - }, - "name": "len" - }, - { - "layout": { - "array": { - "cellsPerElem": 1, - "layout": { - "cell": { - "key": "0x0400000002000000000000000000000000000000000000000000000000000000", - "ty": 10 - } - }, - "len": 4294967295, - "offset": "0x0500000001000000000000000000000000000000000000000000000000000000" - } - }, - "name": "elems" - } - ] - } - }, - "name": "entries" - } - ] - } - }, - "name": "keys" - }, - { - "layout": { - "hash": { - "layout": { - "cell": { - "key": "0x0500000002000000000000000000000000000000000000000000000000000000", - "ty": 9 - } - }, - "offset": "0x0400000002000000000000000000000000000000000000000000000000000000", - "strategy": { - "hasher": "Blake2x256", - "postfix": "", - "prefix": "0x696e6b20686173686d6170" - } - } - }, - "name": "values" - } - ] - } - }, - "name": "allowances" - } - ] - } - }, - "types": [ - { - "def": { - "primitive": "u128" - } - }, - { - "def": { - "composite": { - "fields": [ - { - "name": "last_vacant", - "type": 3, - "typeName": "Index" - }, - { - "name": "len", - "type": 3, - "typeName": "u32" - }, - { - "name": "len_entries", - "type": 3, - "typeName": "u32" - } - ] - } - }, - "path": [ - "ink_storage", - "collections", - "stash", - "Header" - ] - }, - { - "def": { - "primitive": "u32" - } - }, - { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 8, - "typeName": "VacantEntry" - } - ], - "name": "Vacant" - }, - { - "fields": [ - { - "type": 5, - "typeName": "T" - } - ], - "name": "Occupied" - } - ] - } - }, - "params": [ - 5 - ], - "path": [ - "ink_storage", - "collections", - "stash", - "Entry" - ] - }, - { - "def": { - "composite": { - "fields": [ - { - "type": 6, - "typeName": "[u8; 32]" - } - ] - } - }, - "path": [ - "ink_env", - "types", - "AccountId" - ] - }, - { - "def": { - "array": { - "len": 32, - "type": 7 - } - } - }, - { - "def": { - "primitive": "u8" - } - }, - { - "def": { - "composite": { - "fields": [ - { - "name": "next", - "type": 3, - "typeName": "Index" - }, - { - "name": "prev", - "type": 3, - "typeName": "Index" - } - ] - } - }, - "path": [ - "ink_storage", - "collections", - "stash", - "VacantEntry" - ] - }, - { - "def": { - "composite": { - "fields": [ - { - "name": "value", - "type": 1, - "typeName": "V" - }, - { - "name": "key_index", - "type": 3, - "typeName": "KeyIndex" - } - ] - } - }, - "params": [ - 1 - ], - "path": [ - "ink_storage", - "collections", - "hashmap", - "ValueEntry" - ] - }, - { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 8, - "typeName": "VacantEntry" - } - ], - "name": "Vacant" - }, - { - "fields": [ - { - "type": 11, - "typeName": "T" - } - ], - "name": "Occupied" - } - ] - } - }, - "params": [ - 11 - ], - "path": [ - "ink_storage", - "collections", - "stash", - "Entry" - ] - }, - { - "def": { - "tuple": [ - 5, - 5 - ] - } - }, - { - "def": { - "variant": { - "variants": [ - { - "fields": [ - { - "type": 13, - "typeName": "T" - } - ], - "name": "Ok" - }, - { - "fields": [ - { - "type": 14, - "typeName": "E" - } - ], - "name": "Err" - } - ] - } - }, - "params": [ - 13, - 14 - ], - "path": [ - "Result" - ] - }, - { - "def": { - "tuple": [] - } - }, - { - "def": { - "variant": { - "variants": [ - { - "discriminant": 0, - "name": "InsufficientBalance" - }, - { - "discriminant": 1, - "name": "InsufficientAllowance" - } - ] - } - }, - "path": [ - "erc20", - "erc20", - "Error" - ] - }, - { - "def": { - "variant": { - "variants": [ - { - "name": "None" - }, - { - "fields": [ - { - "type": 5, - "typeName": "T" - } - ], - "name": "Some" - } - ] - } - }, - "params": [ - 5 - ], - "path": [ - "Option" - ] - } - ] -} \ No newline at end of file diff --git a/substrate/frame/contracts/benchmarks/ink_erc20.wasm b/substrate/frame/contracts/benchmarks/ink_erc20.wasm deleted file mode 100644 index ffd522760a02dd8416f8ee04db02c32e2172b8ff..0000000000000000000000000000000000000000 Binary files a/substrate/frame/contracts/benchmarks/ink_erc20.wasm and /dev/null differ diff --git a/substrate/frame/contracts/benchmarks/ink_erc20_test.wasm b/substrate/frame/contracts/benchmarks/ink_erc20_test.wasm deleted file mode 100644 index f5d84552960a348db3935e457adc4b6fba249d17..0000000000000000000000000000000000000000 Binary files a/substrate/frame/contracts/benchmarks/ink_erc20_test.wasm and /dev/null differ diff --git a/substrate/frame/contracts/benchmarks/solang_erc20.json b/substrate/frame/contracts/benchmarks/solang_erc20.json deleted file mode 100644 index 9d8fd5ce70e70edb0fdb96439be847369e6ae7e0..0000000000000000000000000000000000000000 --- a/substrate/frame/contracts/benchmarks/solang_erc20.json +++ /dev/null @@ -1,581 +0,0 @@ -{ - "contract": { - "authors": [ - "unknown" - ], - "name": "ERC20PresetFixedSupply", - "version": "0.0.1" - }, - "metadataVersion": "0.1.0", - "source": { - "compiler": "solang 0.1.7", - "hash": "0x9c55e342566e89c741eb641eec3af796836da750fc930c55bccc0604a47ef700", - "language": "Solidity 0.1.7" - }, - "spec": { - "constructors": [ - { - "args": [ - { - "name": "name", - "type": { - "display_name": [ - "String" - ], - "type": 2 - } - }, - { - "name": "symbol", - "type": { - "display_name": [ - "String" - ], - "type": 2 - } - }, - { - "name": "initialSupply", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - }, - { - "name": "owner", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - "" - ], - "name": "new", - "selector": "0xa6f1f5e1" - } - ], - "events": [ - { - "args": [ - { - "indexed": true, - "name": "owner", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "indexed": true, - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "indexed": false, - "name": "value", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "name": "Approval" - }, - { - "args": [ - { - "indexed": true, - "name": "from", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "indexed": true, - "name": "to", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "indexed": false, - "name": "value", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "name": "Transfer" - } - ], - "messages": [ - { - "args": [ - { - "name": "account", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "burnFrom", - "payable": false, - "return_type": null, - "selector": "0x0f1354f3" - }, - { - "args": [ - { - "name": "account", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - "" - ], - "mutates": false, - "name": "balanceOf", - "payable": false, - "return_type": { - "display_name": [ - "u256" - ], - "type": 1 - }, - "selector": "0x6c7f1542" - }, - { - "args": [], - "docs": [ - "" - ], - "mutates": false, - "name": "totalSupply", - "payable": false, - "return_type": { - "display_name": [ - "u256" - ], - "type": 1 - }, - "selector": "0x18160ddd" - }, - { - "args": [], - "docs": [ - "" - ], - "mutates": false, - "name": "decimals", - "payable": false, - "return_type": { - "display_name": [ - "u8" - ], - "type": 3 - }, - "selector": "0x313ce567" - }, - { - "args": [ - { - "name": "owner", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - } - ], - "docs": [ - "" - ], - "mutates": false, - "name": "allowance", - "payable": false, - "return_type": { - "display_name": [ - "u256" - ], - "type": 1 - }, - "selector": "0xf2a9a8c7" - }, - { - "args": [], - "docs": [ - "" - ], - "mutates": false, - "name": "name", - "payable": false, - "return_type": { - "display_name": [ - "String" - ], - "type": 2 - }, - "selector": "0x06fdde03" - }, - { - "args": [ - { - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "subtractedValue", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "decreaseAllowance", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0x4b76697b" - }, - { - "args": [ - { - "name": "sender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "recipient", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "transferFrom", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0x2fb840f5" - }, - { - "args": [], - "docs": [ - "" - ], - "mutates": false, - "name": "symbol", - "payable": false, - "return_type": { - "display_name": [ - "String" - ], - "type": 2 - }, - "selector": "0x95d89b41" - }, - { - "args": [ - { - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "addedValue", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "increaseAllowance", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0xb936c899" - }, - { - "args": [ - { - "name": "recipient", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "transfer", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0x6a467394" - }, - { - "args": [ - { - "name": "spender", - "type": { - "display_name": [ - "AccountId" - ], - "type": 5 - } - }, - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "approve", - "payable": false, - "return_type": { - "display_name": [ - "bool" - ], - "type": 6 - }, - "selector": "0x47144421" - }, - { - "args": [ - { - "name": "amount", - "type": { - "display_name": [ - "u256" - ], - "type": 1 - } - } - ], - "docs": [ - "" - ], - "mutates": true, - "name": "burn", - "payable": false, - "return_type": null, - "selector": "0x42966c68" - } - ] - }, - "storage": { - "struct": { - "fields": [ - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000002", - "ty": 1 - } - }, - "name": "_totalSupply" - }, - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000003", - "ty": 2 - } - }, - "name": "_name" - }, - { - "layout": { - "cell": { - "key": "0x0000000000000000000000000000000000000000000000000000000000000004", - "ty": 2 - } - }, - "name": "_symbol" - } - ] - } - }, - "types": [ - { - "def": { - "primitive": "u256" - } - }, - { - "def": { - "primitive": "str" - } - }, - { - "def": { - "primitive": "u8" - } - }, - { - "def": { - "array": { - "len": 32, - "type": 3 - } - } - }, - { - "def": { - "composite": { - "fields": [ - { - "type": 4 - } - ] - } - }, - "path": [ - "AccountId" - ] - }, - { - "def": { - "primitive": "bool" - } - } - ] -} diff --git a/substrate/frame/contracts/benchmarks/solang_erc20.wasm b/substrate/frame/contracts/benchmarks/solang_erc20.wasm deleted file mode 100644 index 0796085d33249b78871f4a336623444a0ef94e85..0000000000000000000000000000000000000000 Binary files a/substrate/frame/contracts/benchmarks/solang_erc20.wasm and /dev/null differ diff --git a/substrate/frame/contracts/fixtures/contracts/call.rs b/substrate/frame/contracts/fixtures/contracts/call.rs index f7d9d862d7465937f71afff998aecdaa9aa1f4bd..535745ffc0bc505c5cc29dc5959fe31a3e1fe509 100644 --- a/substrate/frame/contracts/fixtures/contracts/call.rs +++ b/substrate/frame/contracts/fixtures/contracts/call.rs @@ -35,11 +35,13 @@ pub extern "C" fn call() { ); // Call the callee - api::call_v1( + api::call_v2( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &0u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/call_return_code.rs b/substrate/frame/contracts/fixtures/contracts/call_return_code.rs index 1256588d3b58554edf5b1d00e98572681087f2c7..3d5a1073eaecaedabcf774e336abea48ab0b9001 100644 --- a/substrate/frame/contracts/fixtures/contracts/call_return_code.rs +++ b/substrate/frame/contracts/fixtures/contracts/call_return_code.rs @@ -38,11 +38,13 @@ pub extern "C" fn call() { ); // Call the callee - let err_code = match api::call_v1( + let err_code = match api::call_v2( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &100u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &100u64.to_le_bytes(), // Value transferred to the contract. input, None, ) { diff --git a/substrate/frame/contracts/fixtures/contracts/call_runtime_and_call.rs b/substrate/frame/contracts/fixtures/contracts/call_runtime_and_call.rs index fdeb6097e732f9e7cc717f1144a51953c138e7f7..1321d36dc7efa17151cff67cfcd6162c91eecaa6 100644 --- a/substrate/frame/contracts/fixtures/contracts/call_runtime_and_call.rs +++ b/substrate/frame/contracts/fixtures/contracts/call_runtime_and_call.rs @@ -39,11 +39,13 @@ pub extern "C" fn call() { api::call_runtime(call).unwrap(); // Call the callee - api::call_v1( + api::call_v2( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &0u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs b/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs index 5e98aa614e95acad471a4146c492ca12cee90da8..f0851f32c75bc13d7b89c1bf366595ef1dbd57ca 100644 --- a/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs +++ b/substrate/frame/contracts/fixtures/contracts/call_with_limit.rs @@ -31,12 +31,13 @@ pub extern "C" fn deploy() {} #[polkavm_derive::polkavm_export] pub extern "C" fn call() { input!( + 256, callee_addr: [u8; 32], ref_time: u64, proof_size: u64, + forwarded_input: [u8], ); - #[allow(deprecated)] api::call_v2( uapi::CallFlags::empty(), callee_addr, @@ -44,7 +45,7 @@ pub extern "C" fn call() { proof_size, None, // No deposit limit. &0u64.to_le_bytes(), // value transferred to the contract. - &[0u8; 0], // input data. + forwarded_input, None, ) .unwrap(); diff --git a/substrate/frame/contracts/fixtures/contracts/caller_contract.rs b/substrate/frame/contracts/fixtures/contracts/caller_contract.rs index c2629e9fa1971fea3da5a77f3c2d3bdfce3ce601..fffdb66a33d7979da3d81f53ec51e4308f5d355b 100644 --- a/substrate/frame/contracts/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/contracts/fixtures/contracts/caller_contract.rs @@ -40,7 +40,6 @@ pub extern "C" fn call() { let reverted_input = [1u8, 34, 51, 68, 85, 102, 119]; // Fail to deploy the contract since it returns a non-zero exit status. - #[allow(deprecated)] let res = api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. @@ -55,7 +54,6 @@ pub extern "C" fn call() { assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); // Fail to deploy the contract due to insufficient ref_time weight. - #[allow(deprecated)] let res = api::instantiate_v2( code_hash, 1u64, // too little ref_time weight 0u64, // How much proof_size weight to devote for the execution. 0 = all. @@ -65,7 +63,6 @@ pub extern "C" fn call() { assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); // Fail to deploy the contract due to insufficient proof_size weight. - #[allow(deprecated)] let res = api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 1u64, // Too little proof_size weight @@ -78,7 +75,6 @@ pub extern "C" fn call() { let mut callee = [0u8; 32]; let callee = &mut &mut callee[..]; - #[allow(deprecated)] api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. @@ -94,7 +90,6 @@ pub extern "C" fn call() { assert_eq!(callee.len(), 32); // Call the new contract and expect it to return failing exit code. - #[allow(deprecated)] let res = api::call_v2( uapi::CallFlags::empty(), callee, @@ -108,11 +103,10 @@ pub extern "C" fn call() { assert!(matches!(res, Err(ReturnErrorCode::CalleeReverted))); // Fail to call the contract due to insufficient ref_time weight. - #[allow(deprecated)] let res = api::call_v2( uapi::CallFlags::empty(), callee, - 1u64, // too little ref_time weight + 1u64, // Too little ref_time weight. 0u64, // How much proof_size weight to devote for the execution. 0 = all. None, // No deposit limit. &value, @@ -122,7 +116,6 @@ pub extern "C" fn call() { assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); // Fail to call the contract due to insufficient proof_size weight. - #[allow(deprecated)] let res = api::call_v2( uapi::CallFlags::empty(), callee, @@ -137,7 +130,6 @@ pub extern "C" fn call() { // Call the contract successfully. let mut output = [0u8; 4]; - #[allow(deprecated)] api::call_v2( uapi::CallFlags::empty(), callee, diff --git a/substrate/frame/contracts/fixtures/contracts/chain_extension_temp_storage.rs b/substrate/frame/contracts/fixtures/contracts/chain_extension_temp_storage.rs index 1ab08efb3c7ea49ee1e016cb6136b428e86075a3..2e15fb02a12ca5f0cd4bee944af062961d66e8b2 100644 --- a/substrate/frame/contracts/fixtures/contracts/chain_extension_temp_storage.rs +++ b/substrate/frame/contracts/fixtures/contracts/chain_extension_temp_storage.rs @@ -50,11 +50,13 @@ pub extern "C" fn call() { output!(addr, [0u8; 32], api::address,); // call self - api::call_v1( + api::call_v2( uapi::CallFlags::ALLOW_REENTRY, addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &0u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/create_storage_and_call.rs b/substrate/frame/contracts/fixtures/contracts/create_storage_and_call.rs index 8b79dd87dffd2f35c2c597f95f4e442a855f69ea..f8ce0ff4fb84831fbf258f489d0bc6935784ff43 100644 --- a/substrate/frame/contracts/fixtures/contracts/create_storage_and_call.rs +++ b/substrate/frame/contracts/fixtures/contracts/create_storage_and_call.rs @@ -40,14 +40,13 @@ pub extern "C" fn call() { api::set_storage(buffer, &[1u8; 4]); // Call the callee - #[allow(deprecated)] api::call_v2( uapi::CallFlags::empty(), callee, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, // How much proof_size weight to devote for the execution. 0 = all. Some(deposit_limit), - &0u64.to_le_bytes(), // value transferred to the contract. + &0u64.to_le_bytes(), // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/contracts/fixtures/contracts/create_storage_and_instantiate.rs index c68d99eff821e86ecfa399fc6804f1e3a7b2649c..fa3b9000a7ed6bd3f5f094d39b5d025a995a8b4c 100644 --- a/substrate/frame/contracts/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/contracts/fixtures/contracts/create_storage_and_instantiate.rs @@ -40,7 +40,6 @@ pub extern "C" fn call() { let mut address = [0u8; 32]; let address = &mut &mut address[..]; - #[allow(deprecated)] api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. diff --git a/substrate/frame/contracts/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/contracts/fixtures/contracts/destroy_and_transfer.rs index cfcfd60f21eef1c543e249832f74bd75e5c4a8d3..62fb63b56020455ea775e802b95daeacaefd3658 100644 --- a/substrate/frame/contracts/fixtures/contracts/destroy_and_transfer.rs +++ b/substrate/frame/contracts/fixtures/contracts/destroy_and_transfer.rs @@ -34,7 +34,6 @@ pub extern "C" fn deploy() { let address = &mut &mut address[..]; let salt = [71u8, 17u8]; - #[allow(deprecated)] api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. @@ -60,7 +59,6 @@ pub extern "C" fn call() { api::get_storage(&ADDRESS_KEY, callee_addr).unwrap(); // Calling the destination contract with non-empty input data should fail. - #[allow(deprecated)] let res = api::call_v2( uapi::CallFlags::empty(), callee_addr, @@ -74,7 +72,6 @@ pub extern "C" fn call() { assert!(matches!(res, Err(uapi::ReturnErrorCode::CalleeTrapped))); // Call the destination contract regularly, forcing it to self-destruct. - #[allow(deprecated)] api::call_v2( uapi::CallFlags::empty(), callee_addr, diff --git a/substrate/frame/contracts/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/contracts/fixtures/contracts/instantiate_return_code.rs index 67cd739d85a6942a046cfaf7cc45301d6570600f..de194abe4840ac5fc67b83c1a377d114e65dfc01 100644 --- a/substrate/frame/contracts/fixtures/contracts/instantiate_return_code.rs +++ b/substrate/frame/contracts/fixtures/contracts/instantiate_return_code.rs @@ -31,7 +31,6 @@ pub extern "C" fn call() { input!(buffer, 36, code_hash: [u8; 32],); let input = &buffer[32..]; - #[allow(deprecated)] let err_code = match api::instantiate_v2( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. diff --git a/substrate/frame/contracts/fixtures/contracts/add_remove_delegate_dependency.rs b/substrate/frame/contracts/fixtures/contracts/locking_delegate_dependency.rs similarity index 83% rename from substrate/frame/contracts/fixtures/contracts/add_remove_delegate_dependency.rs rename to substrate/frame/contracts/fixtures/contracts/locking_delegate_dependency.rs index 759ff7993740479f9e89b3c047eca27de74dbcc4..bb76c942aad18c511ab2e88bf1597101b307c2b4 100644 --- a/substrate/frame/contracts/fixtures/contracts/add_remove_delegate_dependency.rs +++ b/substrate/frame/contracts/fixtures/contracts/locking_delegate_dependency.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! This contract tests the behavior of adding / removing delegate_dependencies when delegate +//! This contract tests the behavior of locking / unlocking delegate_dependencies when delegate //! calling into a contract. #![no_std] #![no_main] @@ -34,15 +34,13 @@ fn load_input(delegate_call: bool) { ); match action { - // 1 = Add delegate dependency + // 1 = Lock delegate dependency 1 => { - #[allow(deprecated)] - api::add_delegate_dependency(code_hash); + api::lock_delegate_dependency(code_hash); }, - // 2 = Remove delegate dependency + // 2 = Unlock delegate dependency 2 => { - #[allow(deprecated)] - api::remove_delegate_dependency(code_hash); + api::unlock_delegate_dependency(code_hash); }, // 3 = Terminate 3 => { diff --git a/substrate/frame/contracts/fixtures/contracts/recurse.rs b/substrate/frame/contracts/fixtures/contracts/recurse.rs new file mode 100644 index 0000000000000000000000000000000000000000..b1ded608c2fc417323580257b74835a0aaee9aa3 --- /dev/null +++ b/substrate/frame/contracts/fixtures/contracts/recurse.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls itself as many times as passed as argument. + +#![no_std] +#![no_main] + +use common::{input, output}; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(calls_left: u32, ); + + // own address + output!(addr, [0u8; 32], api::address,); + + if calls_left == 0 { + return + } + + api::call_v2( + uapi::CallFlags::ALLOW_REENTRY, + addr, + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much deposit_limit to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. + &(calls_left - 1).to_le_bytes(), + None, + ) + .unwrap(); +} diff --git a/substrate/frame/contracts/fixtures/contracts/reentrance_count_call.rs b/substrate/frame/contracts/fixtures/contracts/reentrance_count_call.rs index 9812dce2e818d29a352927d2b47e301b06683dae..0acfe017f8df9094ef14aa9e6947b56e183d25be 100644 --- a/substrate/frame/contracts/fixtures/contracts/reentrance_count_call.rs +++ b/substrate/frame/contracts/fixtures/contracts/reentrance_count_call.rs @@ -42,11 +42,13 @@ pub extern "C" fn call() { if expected_reentrance_count != 5 { let count = (expected_reentrance_count + 1).to_le_bytes(); - api::call_v1( + api::call_v2( uapi::CallFlags::ALLOW_REENTRY, addr, - 0u64, // How much gas to devote for the execution. 0 = all. - &0u64.to_le_bytes(), // value transferred to the contract. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. + &0u64.to_le_bytes(), // Value transferred to the contract. &count, None, ) diff --git a/substrate/frame/contracts/fixtures/contracts/self_destruct.rs b/substrate/frame/contracts/fixtures/contracts/self_destruct.rs index 94c3ac387a0a65228674efa93a477543b807661c..d3836d2d8c734214a67333dba9293067b4b5d9b1 100644 --- a/substrate/frame/contracts/fixtures/contracts/self_destruct.rs +++ b/substrate/frame/contracts/fixtures/contracts/self_destruct.rs @@ -37,10 +37,12 @@ pub extern "C" fn call() { if !input.is_empty() { output!(addr, [0u8; 32], api::address,); - api::call_v1( + api::call_v2( uapi::CallFlags::ALLOW_REENTRY, addr, - 0u64, // How much gas to devote for the execution. 0 = all. + 0u64, // How much ref_time to devote for the execution. 0 = all. + 0u64, // How much proof_size to devote for the execution. 0 = all. + None, // No deposit limit. &0u64.to_le_bytes(), // Value to transfer. &[0u8; 0], None, diff --git a/substrate/frame/contracts/fixtures/contracts/xcm_execute.rs b/substrate/frame/contracts/fixtures/contracts/xcm_execute.rs index 09d0b6cf972815e093f066a506e3d06e5b447791..1d570ffead71845c77d664bf5b911bf66794e907 100644 --- a/substrate/frame/contracts/fixtures/contracts/xcm_execute.rs +++ b/substrate/frame/contracts/fixtures/contracts/xcm_execute.rs @@ -30,10 +30,11 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!(512, msg: [u8],); - let mut outcome = [0u8; 512]; - let outcome = &mut &mut outcome[..]; - #[allow(deprecated)] - api::xcm_execute(msg, outcome).unwrap(); - api::return_value(uapi::ReturnFlags::empty(), outcome); + let err_code = match api::xcm_execute(msg) { + Ok(_) => 0u32, + Err(code) => code as u32, + }; + + api::return_value(uapi::ReturnFlags::empty(), &err_code.to_le_bytes()); } diff --git a/substrate/frame/contracts/mock-network/src/lib.rs b/substrate/frame/contracts/mock-network/src/lib.rs index eea9dde062c83172dbc106333f053657a7267cda..8a17a3f2fa781703f1393f2d69ca955384a080a2 100644 --- a/substrate/frame/contracts/mock-network/src/lib.rs +++ b/substrate/frame/contracts/mock-network/src/lib.rs @@ -26,7 +26,8 @@ use crate::primitives::{AccountId, UNITS}; use sp_runtime::BuildStorage; use xcm::latest::prelude::*; use xcm_executor::traits::ConvertLocation; -use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt}; +pub use xcm_simulator::TestExt; +use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain}; // Accounts pub const ADMIN: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]); diff --git a/substrate/frame/contracts/mock-network/src/parachain.rs b/substrate/frame/contracts/mock-network/src/parachain.rs index 53839f1fca037e370a598efa490cd2f226ac5b10..7a60a66b3145dd7268f20924cd53ec81f2d32447 100644 --- a/substrate/frame/contracts/mock-network/src/parachain.rs +++ b/substrate/frame/contracts/mock-network/src/parachain.rs @@ -37,13 +37,11 @@ use sp_runtime::traits::{Get, IdentityLookup, MaybeEquivalence}; use sp_std::prelude::*; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, ConvertedConcreteId, EnsureXcmOrigin, FixedRateOfFungible, FixedWeightBounds, - FrameTransactionalProcessor, FungiblesAdapter, IsConcrete, NativeAsset, NoChecking, - ParentAsSuperuser, ParentIsPreset, SignedAccountId32AsNative, SignedToAccountId32, + FrameTransactionalProcessor, FungibleAdapter, FungiblesAdapter, IsConcrete, NativeAsset, + NoChecking, ParentAsSuperuser, ParentIsPreset, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{traits::JustTry, Config, XcmExecutor}; @@ -184,9 +182,8 @@ pub fn estimate_fee_for_weight(weight: Weight) -> u128 { units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128) } -#[allow(deprecated)] pub type LocalBalancesTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + FungibleAdapter, SovereignAccountOf, AccountId, ()>; pub struct FromLocationToAsset(PhantomData<(Location, AssetId)>); impl MaybeEquivalence diff --git a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs index dadba394e26453366d0b90ce8ff18931ab00743f..3f26c6f372ef5d04501332d76856c20437a398e3 100644 --- a/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs +++ b/substrate/frame/contracts/mock-network/src/parachain/contracts_config.rs @@ -94,5 +94,6 @@ impl pallet_contracts::Config for Runtime { type WeightPrice = Self; type Debug = (); type Environment = (); + type ApiVersion = (); type Xcm = pallet_xcm::Pallet; } diff --git a/substrate/frame/contracts/mock-network/src/relay_chain.rs b/substrate/frame/contracts/mock-network/src/relay_chain.rs index ce7e22dce2f0ccba24a6860408b0fc0c112eb3b9..6eb9b4e53855c0c62e65392a1dd0a63adc239b0b 100644 --- a/substrate/frame/contracts/mock-network/src/relay_chain.rs +++ b/substrate/frame/contracts/mock-network/src/relay_chain.rs @@ -29,13 +29,11 @@ use sp_runtime::traits::IdentityLookup; use polkadot_parachain_primitives::primitives::Id as ParaId; use polkadot_runtime_parachains::{configuration, origin, shared}; use xcm::latest::prelude::*; -#[allow(deprecated)] -use xcm_builder::CurrencyAdapter as XcmCurrencyAdapter; use xcm_builder::{ AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser, DescribeAllTerminal, DescribeFamily, FixedRateOfFungible, - FixedWeightBounds, FrameTransactionalProcessor, HashedDescription, IsConcrete, + FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, WithComputedOrigin, }; use xcm_executor::{Config, XcmExecutor}; @@ -119,9 +117,8 @@ pub type SovereignAccountOf = ( ChildParachainConvertsVia, ); -#[allow(deprecated)] pub type LocalBalancesTransactor = - XcmCurrencyAdapter, SovereignAccountOf, AccountId, ()>; + FungibleAdapter, SovereignAccountOf, AccountId, ()>; pub type AssetTransactors = LocalBalancesTransactor; diff --git a/substrate/frame/contracts/mock-network/src/tests.rs b/substrate/frame/contracts/mock-network/src/tests.rs index c25373bcbe3aeaaad5d0ac7396e70ed61a4729c6..d22221fe8ee051182471249182d54b02b7f47c2c 100644 --- a/substrate/frame/contracts/mock-network/src/tests.rs +++ b/substrate/frame/contracts/mock-network/src/tests.rs @@ -21,7 +21,6 @@ use crate::{ primitives::{AccountId, CENTS}, relay_chain, MockNet, ParaA, ParachainBalances, Relay, ALICE, BOB, INITIAL_BALANCE, }; -use assert_matches::assert_matches; use codec::{Decode, Encode}; use frame_support::{ assert_err, @@ -31,11 +30,18 @@ use frame_support::{ use pallet_balances::{BalanceLock, Reasons}; use pallet_contracts::{Code, CollectEvents, DebugInfo, Determinism}; use pallet_contracts_fixtures::compile_module; +use pallet_contracts_uapi::ReturnErrorCode; use xcm::{v4::prelude::*, VersionedLocation, VersionedXcm}; use xcm_simulator::TestExt; type ParachainContracts = pallet_contracts::Pallet; +macro_rules! assert_return_code { + ( $x:expr , $y:expr $(,)? ) => {{ + assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); + }}; +} + /// Instantiate the tests contract, and fund it with some balance and assets. fn instantiate_test_contract(name: &str) -> AccountId { let (wasm, _) = compile_module::(name).unwrap(); @@ -100,19 +106,57 @@ fn test_xcm_execute() { DebugInfo::UnsafeDebug, CollectEvents::UnsafeCollect, Determinism::Enforced, - ) - .result - .unwrap(); + ); - let mut data = &result.data[..]; - let outcome = Outcome::decode(&mut data).expect("Failed to decode xcm_execute Outcome"); - assert_matches!(outcome, Outcome::Complete { .. }); + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::Success); // Check if the funds are subtracted from the account of Alice and added to the account of // Bob. let initial = INITIAL_BALANCE; - assert_eq!(parachain::Assets::balance(0, contract_addr), initial); assert_eq!(ParachainBalances::free_balance(BOB), initial + amount); + assert_eq!(ParachainBalances::free_balance(&contract_addr), initial - amount); + }); +} + +#[test] +fn test_xcm_execute_incomplete() { + MockNet::reset(); + + let contract_addr = instantiate_test_contract("xcm_execute"); + let amount = 10 * CENTS; + + // Execute XCM instructions through the contract. + ParaA::execute_with(|| { + // The XCM used to transfer funds to Bob. + let message: Xcm<()> = Xcm(vec![ + WithdrawAsset(vec![(Here, amount).into()].into()), + // This will fail as the contract does not have enough balance to complete both + // withdrawals. + WithdrawAsset(vec![(Here, INITIAL_BALANCE).into()].into()), + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { network: None, id: BOB.clone().into() }.into(), + }, + ]); + + let result = ParachainContracts::bare_call( + ALICE, + contract_addr.clone(), + 0, + Weight::MAX, + None, + VersionedXcm::V4(message).encode(), + DebugInfo::UnsafeDebug, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + assert_eq!(result.gas_consumed, result.gas_required); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); + + assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); + assert_eq!(ParachainBalances::free_balance(&contract_addr), INITIAL_BALANCE - amount); }); } @@ -182,17 +226,10 @@ fn test_xcm_execute_reentrant_call() { DebugInfo::UnsafeDebug, CollectEvents::UnsafeCollect, Determinism::Enforced, - ) - .result - .unwrap(); - - let mut data = &result.data[..]; - let outcome = Outcome::decode(&mut data).expect("Failed to decode xcm_execute Outcome"); - assert_matches!( - outcome, - Outcome::Incomplete { used: _, error: XcmError::ExpectationFalse } ); + assert_return_code!(&result.result.unwrap(), ReturnErrorCode::XcmExecutionFailed); + // Funds should not change hands as the XCM transact failed. assert_eq!(ParachainBalances::free_balance(BOB), INITIAL_BALANCE); }); diff --git a/substrate/frame/contracts/proc-macro/Cargo.toml b/substrate/frame/contracts/proc-macro/Cargo.toml index ed6175ee8cdfdf34fff9faa0ca00341c65191ee7..4080cd0442dbc516231dc983a3b6609a211523db 100644 --- a/substrate/frame/contracts/proc-macro/Cargo.toml +++ b/substrate/frame/contracts/proc-macro/Cargo.toml @@ -19,5 +19,5 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.48", features = ["full"] } +quote = { workspace = true } +syn = { features = ["full"], workspace = true } diff --git a/substrate/frame/contracts/proc-macro/src/lib.rs b/substrate/frame/contracts/proc-macro/src/lib.rs index 403db15ac2cbcf796280a29ba4c7a91e3047b21e..de961776c322a84d6025d691419c9423fda800db 100644 --- a/substrate/frame/contracts/proc-macro/src/lib.rs +++ b/substrate/frame/contracts/proc-macro/src/lib.rs @@ -638,37 +638,34 @@ fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2) }; let sync_gas_before = if expand_blocks { quote! { - // Gas left in the gas meter right before switching to engine execution. - let __gas_before__ = { - let engine_consumed_total = + // Write gas from wasmi into pallet-contracts before entering the host function. + let __gas_left_before__ = { + let executor_total = __caller__.fuel_consumed().expect("Fuel metering is enabled; qed"); - let gas_meter = __caller__.data_mut().ext().gas_meter_mut(); - gas_meter - .charge_fuel(engine_consumed_total) + __caller__ + .data_mut() + .ext() + .gas_meter_mut() + .sync_from_executor(executor_total) .map_err(TrapReason::from) .map_err(#into_host)? - .ref_time() }; } } else { quote! { } }; - // Gas left in the gas meter right after returning from engine execution. + // Write gas from pallet-contracts into wasmi after leaving the host function. let sync_gas_after = if expand_blocks { quote! { - let mut gas_after = __caller__.data_mut().ext().gas_meter().gas_left().ref_time(); - let mut host_consumed = __gas_before__.saturating_sub(gas_after); - // Possible undercharge of at max 1 fuel here, if host consumed less than `instruction_weights.base` - // Not a problem though, as soon as host accounts its spent gas properly. - let fuel_consumed = host_consumed - .checked_div(__caller__.data_mut().ext().schedule().instruction_weights.base as u64) - .ok_or(Error::::InvalidSchedule) - .map_err(TrapReason::from) - .map_err(#into_host)?; + let fuel_consumed = __caller__ + .data_mut() + .ext() + .gas_meter_mut() + .sync_to_executor(__gas_left_before__) + .map_err(TrapReason::from)?; __caller__ - .consume_fuel(fuel_consumed) - .map_err(|_| TrapReason::from(Error::::OutOfGas)) - .map_err(#into_host)?; + .consume_fuel(fuel_consumed.into()) + .map_err(|_| TrapReason::from(Error::::OutOfGas))?; } } else { quote! { } diff --git a/substrate/frame/contracts/src/benchmarking/code.rs b/substrate/frame/contracts/src/benchmarking/code.rs index 0b6ab6b3c0e1b99b2dfb34642568d29cc9dfdd91..644c2abf0a8d5a49443f26e38b8869c134a7805f 100644 --- a/substrate/frame/contracts/src/benchmarking/code.rs +++ b/substrate/frame/contracts/src/benchmarking/code.rs @@ -31,8 +31,8 @@ use sp_std::{borrow::ToOwned, prelude::*}; use wasm_instrument::parity_wasm::{ builder, elements::{ - self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Local, - Module, Section, ValueType, + self, BlockType, CustomSection, FuncBody, Instruction, Instructions, Local, Section, + ValueType, }, }; @@ -238,24 +238,6 @@ impl From for WasmModule { } impl WasmModule { - /// Uses the supplied wasm module. - pub fn from_code(code: &[u8]) -> Self { - let module = Module::from_bytes(code).unwrap(); - let limits = *module - .import_section() - .unwrap() - .entries() - .iter() - .find_map(|e| if let External::Memory(mem) = e.external() { Some(mem) } else { None }) - .unwrap() - .limits(); - let code = module.into_bytes().unwrap(); - let hash = T::Hashing::hash(&code); - let memory = - ImportedMemory { min_pages: limits.initial(), max_pages: limits.maximum().unwrap() }; - Self { code: code.into(), hash, memory: Some(memory) } - } - /// Creates a wasm module with an empty `call` and `deploy` function and nothing else. pub fn dummy() -> Self { ModuleDefinition::default().into() diff --git a/substrate/frame/contracts/src/benchmarking/mod.rs b/substrate/frame/contracts/src/benchmarking/mod.rs index e387c28b6714c02d5ee5831cf5b5030bf90660d5..4b6ff63c6f5d44dfb6a985a222535829a1a4439c 100644 --- a/substrate/frame/contracts/src/benchmarking/mod.rs +++ b/substrate/frame/contracts/src/benchmarking/mod.rs @@ -29,7 +29,7 @@ use self::{ sandbox::Sandbox, }; use crate::{ - exec::{AccountIdOf, Key}, + exec::Key, migration::{ codegen::LATEST_MIGRATION_VERSION, v09, v10, v11, v12, v13, v14, v15, MigrationStep, }, @@ -184,24 +184,6 @@ fn caller_funding() -> BalanceOf { BalanceOf::::max_value() / 10_000u32.into() } -/// Load the specified contract file from disk by including it into the runtime. -/// -/// We need to load a different version of ink! contracts when the benchmark is run as -/// a test. This is because ink! contracts depend on the sizes of types that are defined -/// differently in the test environment. Solang is more lax in that regard. -macro_rules! load_benchmark { - ($name:expr) => {{ - #[cfg(not(test))] - { - include_bytes!(concat!("../../benchmarks/", $name, ".wasm")) - } - #[cfg(test)] - { - include_bytes!(concat!("../../benchmarks/", $name, "_test.wasm")) - } - }}; -} - benchmarks! { where_clause { where as codec::HasCompact>::Type: Clone + Eq + PartialEq + sp_std::fmt::Debug + scale_info::TypeInfo + codec::Encode, @@ -913,7 +895,7 @@ benchmarks! { }, ImportedFunction { module: "seal0", - name: "add_delegate_dependency", + name: "lock_delegate_dependency", params: vec![ValueType::I32], return_type: None, } @@ -2420,7 +2402,7 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[pov_mode = Measured] - add_delegate_dependency { + lock_delegate_dependency { let r in 0 .. T::MaxDelegateDependencies::get(); let code_hashes = (0..r) .map(|i| { @@ -2438,7 +2420,7 @@ benchmarks! { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal0", - name: "add_delegate_dependency", + name: "lock_delegate_dependency", params: vec![ValueType::I32], return_type: None, }], @@ -2458,7 +2440,7 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - remove_delegate_dependency { + unlock_delegate_dependency { let r in 0 .. T::MaxDelegateDependencies::get(); let code_hashes = (0..r) .map(|i| { @@ -2477,12 +2459,12 @@ benchmarks! { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "seal0", - name: "remove_delegate_dependency", + name: "unlock_delegate_dependency", params: vec![ValueType::I32], return_type: None, }, ImportedFunction { module: "seal0", - name: "add_delegate_dependency", + name: "lock_delegate_dependency", params: vec![ValueType::I32], return_type: None }], @@ -2643,86 +2625,6 @@ benchmarks! { "); }: {} - // Execute one erc20 transfer using the ink! erc20 example contract. - #[extra] - #[pov_mode = Measured] - ink_erc20_transfer { - let code = load_benchmark!("ink_erc20"); - let data = { - let new: ([u8; 4], BalanceOf) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into()); - new.encode() - }; - let instance = Contract::::new( - WasmModule::from_code(code), data, - )?; - let data = { - let transfer: ([u8; 4], AccountIdOf, BalanceOf) = ( - [0x84, 0xa1, 0x5d, 0xa1], - account::("receiver", 0, 0), - 1u32.into(), - ); - transfer.encode() - }; - }: { - >::bare_call( - instance.caller, - instance.account_id, - 0u32.into(), - Weight::MAX, - None, - data, - DebugInfo::Skip, - CollectEvents::Skip, - Determinism::Enforced, - ) - .result?; - } - - // Execute one erc20 transfer using the open zeppelin erc20 contract compiled with solang. - #[extra] - #[pov_mode = Measured] - solang_erc20_transfer { - let code = include_bytes!("../../benchmarks/solang_erc20.wasm"); - let caller = account::("instantiator", 0, 0); - let mut balance = [0u8; 32]; - balance[0] = 100; - let data = { - let new: ([u8; 4], &str, &str, [u8; 32], AccountIdOf) = ( - [0xa6, 0xf1, 0xf5, 0xe1], - "KSM", - "K", - balance, - caller.clone(), - ); - new.encode() - }; - let instance = Contract::::with_caller( - caller, WasmModule::from_code(code), data, - )?; - balance[0] = 1; - let data = { - let transfer: ([u8; 4], AccountIdOf, [u8; 32]) = ( - [0x6a, 0x46, 0x73, 0x94], - account::("receiver", 0, 0), - balance, - ); - transfer.encode() - }; - }: { - >::bare_call( - instance.caller, - instance.account_id, - 0u32.into(), - Weight::MAX, - None, - data, - DebugInfo::Skip, - CollectEvents::Skip, - Determinism::Enforced, - ) - .result?; - } - impl_benchmark_test_suite!( Contracts, crate::tests::ExtBuilder::default().build(), diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 2183d6b96cc5d4f84cbfa6777e0c1b0bbeec4f86..7da321af547d5b8f5539673f7540c328299945b6 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -287,6 +287,9 @@ pub trait Ext: sealing::Sealed { /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. fn append_debug_buffer(&mut self, msg: &str) -> bool; + /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. + fn debug_buffer_enabled(&self) -> bool; + /// Call some dispatchable and return the result. fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; @@ -345,20 +348,20 @@ pub trait Ext: sealing::Sealed { /// - [`Error::::MaxDelegateDependenciesReached`] /// - [`Error::::CannotAddSelfAsDelegateDependency`] /// - [`Error::::DelegateDependencyAlreadyExists`] - fn add_delegate_dependency( + fn lock_delegate_dependency( &mut self, code_hash: CodeHash, ) -> Result<(), DispatchError>; /// Removes a delegate dependency from [`ContractInfo`]'s `delegate_dependencies` field. /// - /// This is the counterpart of [`Self::add_delegate_dependency`]. It decreases the reference - /// count and refunds the deposit that was charged by [`Self::add_delegate_dependency`]. + /// This is the counterpart of [`Self::lock_delegate_dependency`]. It decreases the reference + /// count and refunds the deposit that was charged by [`Self::lock_delegate_dependency`]. /// /// # Errors /// /// - [`Error::::DelegateDependencyNotFound`] - fn remove_delegate_dependency( + fn unlock_delegate_dependency( &mut self, code_hash: &CodeHash, ) -> Result<(), DispatchError>; @@ -834,7 +837,7 @@ where contract_info: CachedContract::Cached(contract_info), account_id, entry_point, - nested_gas: gas_meter.nested(gas_limit)?, + nested_gas: gas_meter.nested(gas_limit), nested_storage: storage_meter.nested(deposit_limit), allows_reentry: true, }; @@ -1429,6 +1432,10 @@ where self.top_frame_mut().nested_storage.charge(diff) } + fn debug_buffer_enabled(&self) -> bool { + self.debug_message.is_some() + } + fn append_debug_buffer(&mut self, msg: &str) -> bool { if let Some(buffer) = &mut self.debug_message { buffer @@ -1547,7 +1554,7 @@ where }); } - fn add_delegate_dependency( + fn lock_delegate_dependency( &mut self, code_hash: CodeHash, ) -> Result<(), DispatchError> { @@ -1558,7 +1565,7 @@ where let code_info = CodeInfoOf::::get(code_hash).ok_or(Error::::CodeNotFound)?; let deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_info.deposit()); - info.add_delegate_dependency(code_hash, deposit)?; + info.lock_delegate_dependency(code_hash, deposit)?; Self::increment_refcount(code_hash)?; frame .nested_storage @@ -1566,14 +1573,14 @@ where Ok(()) } - fn remove_delegate_dependency( + fn unlock_delegate_dependency( &mut self, code_hash: &CodeHash, ) -> Result<(), DispatchError> { let frame = self.top_frame_mut(); let info = frame.contract_info.get(&frame.account_id); - let deposit = info.remove_delegate_dependency(code_hash)?; + let deposit = info.unlock_delegate_dependency(code_hash)?; Self::decrement_refcount(*code_hash); frame .nested_storage diff --git a/substrate/frame/contracts/src/gas.rs b/substrate/frame/contracts/src/gas.rs index 363ddfad975b1eb92c6a4ebcc23982f585eef08c..b9d91f38f16f957ae6bc40ef08010ecf146dbf03 100644 --- a/substrate/frame/contracts/src/gas.rs +++ b/substrate/frame/contracts/src/gas.rs @@ -16,14 +16,14 @@ // limitations under the License. use crate::{exec::ExecError, Config, Error}; +use core::marker::PhantomData; use frame_support::{ dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo}, weights::Weight, DefaultNoBound, }; use sp_core::Get; -use sp_runtime::{traits::Zero, DispatchError}; -use sp_std::marker::PhantomData; +use sp_runtime::{traits::Zero, DispatchError, Saturating}; #[cfg(test)] use std::{any::Any, fmt::Debug}; @@ -37,6 +37,24 @@ impl ChargedAmount { } } +/// Used to capture the gas left before entering a host function. +/// +/// Has to be consumed in order to sync back the gas after leaving the host function. +#[must_use] +pub struct RefTimeLeft(u64); + +/// Resource that needs to be synced to the executor. +/// +/// Wrapped to make sure that the resource will be synced back the the executor. +#[must_use] +pub struct Syncable(u64); + +impl From for u64 { + fn from(from: Syncable) -> u64 { + from.0 + } +} + #[cfg(not(test))] pub trait TestAuxiliaries {} #[cfg(not(test))] @@ -63,6 +81,11 @@ pub trait Token: Copy + Clone + TestAuxiliaries { /// while calculating the amount. In this case it is ok to use saturating operations /// since on overflow they will return `max_value` which should consume all gas. fn weight(&self) -> Weight; + + /// Returns true if this token is expected to influence the lowest gas limit. + fn influence_lowest_gas_limit(&self) -> bool { + true + } } /// A wrapper around a type-erased trait object of what used to be a `Token`. @@ -79,8 +102,13 @@ pub struct GasMeter { gas_left: Weight, /// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value. gas_left_lowest: Weight, - /// Amount of fuel consumed by the engine from the last host function call. - engine_consumed: u64, + /// The amount of resources that was consumed by the execution engine. + /// + /// This should be equivalent to `self.gas_consumed().ref_time()` but expressed in whatever + /// unit the execution engine uses to track resource consumption. We have to track it + /// separately in order to avoid the loss of precision that happens when converting from + /// ref_time to the execution engine unit. + executor_consumed: u64, _phantom: PhantomData, #[cfg(test)] tokens: Vec, @@ -92,7 +120,7 @@ impl GasMeter { gas_limit, gas_left: gas_limit, gas_left_lowest: gas_limit, - engine_consumed: Default::default(), + executor_consumed: 0, _phantom: PhantomData, #[cfg(test)] tokens: Vec::new(), @@ -104,9 +132,7 @@ impl GasMeter { /// # Note /// /// Passing `0` as amount is interpreted as "all remaining gas". - pub fn nested(&mut self, amount: Weight) -> Result { - // NOTE that it is ok to allocate all available gas since it still ensured - // by `charge` that it doesn't reach zero. + pub fn nested(&mut self, amount: Weight) -> Self { let amount = Weight::from_parts( if amount.ref_time().is_zero() { self.gas_left().ref_time() @@ -118,33 +144,17 @@ impl GasMeter { } else { amount.proof_size() }, - ); - self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| >::OutOfGas)?; - Ok(GasMeter::new(amount)) + ) + .min(self.gas_left); + self.gas_left -= amount; + GasMeter::new(amount) } /// Absorb the remaining gas of a nested meter after we are done using it. pub fn absorb_nested(&mut self, nested: Self) { - if self.gas_left.ref_time().is_zero() { - // All of the remaining gas was inherited by the nested gas meter. When absorbing - // we can therefore safely inherit the lowest gas that the nested gas meter experienced - // as long as it is lower than the lowest gas that was experienced by the parent. - // We cannot call `self.gas_left_lowest()` here because in the state that this - // code is run the parent gas meter has `0` gas left. - *self.gas_left_lowest.ref_time_mut() = - nested.gas_left_lowest().ref_time().min(self.gas_left_lowest.ref_time()); - } else { - // The nested gas meter was created with a fixed amount that did not consume all of the - // parents (self) gas. The lowest gas that self will experience is when the nested - // gas was pre charged with the fixed amount. - *self.gas_left_lowest.ref_time_mut() = self.gas_left_lowest().ref_time(); - } - if self.gas_left.proof_size().is_zero() { - *self.gas_left_lowest.proof_size_mut() = - nested.gas_left_lowest().proof_size().min(self.gas_left_lowest.proof_size()); - } else { - *self.gas_left_lowest.proof_size_mut() = self.gas_left_lowest().proof_size(); - } + self.gas_left_lowest = (self.gas_left + nested.gas_limit) + .saturating_sub(nested.gas_required()) + .min(self.gas_left_lowest); self.gas_left += nested.gas_left; } @@ -178,37 +188,48 @@ impl GasMeter { /// This is when a maximum a priori amount was charged and then should be partially /// refunded to match the actual amount. pub fn adjust_gas>(&mut self, charged_amount: ChargedAmount, token: Tok) { - self.gas_left_lowest = self.gas_left_lowest(); + if token.influence_lowest_gas_limit() { + self.gas_left_lowest = self.gas_left_lowest(); + } let adjustment = charged_amount.0.saturating_sub(token.weight()); self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit); } - /// This method is used for gas syncs with the engine. + /// Hand over the gas metering responsibility from the executor to this meter. /// - /// Updates internal `engine_comsumed` tracker of engine fuel consumption. + /// Needs to be called when entering a host function to update this meter with the + /// gas that was tracked by the executor. It tracks the latest seen total value + /// in order to compute the delta that needs to be charged. + pub fn sync_from_executor( + &mut self, + executor_total: u64, + ) -> Result { + let chargable_reftime = executor_total + .saturating_sub(self.executor_consumed) + .saturating_mul(u64::from(T::Schedule::get().instruction_weights.base)); + self.executor_consumed = executor_total; + self.gas_left + .checked_reduce(Weight::from_parts(chargable_reftime, 0)) + .ok_or_else(|| Error::::OutOfGas)?; + Ok(RefTimeLeft(self.gas_left.ref_time())) + } + + /// Hand over the gas metering responsibility from this meter to the executor. /// - /// Charges self with the `ref_time` Weight corresponding to wasmi fuel consumed on the engine - /// side since last sync. Passed value is scaled by multiplying it by the weight of a basic - /// operation, as such an operation in wasmi engine costs 1. + /// Needs to be called when leaving a host function in order to calculate how much + /// gas needs to be charged from the **executor**. It updates the last seen executor + /// total value so that it is correct when `sync_from_executor` is called the next time. /// - /// Returns the updated `gas_left` `Weight` value from the meter. - /// Normally this would never fail, as engine should fail first when out of gas. - pub fn charge_fuel(&mut self, wasmi_fuel_total: u64) -> Result { - // Take the part consumed since the last update. - let wasmi_fuel = wasmi_fuel_total.saturating_sub(self.engine_consumed); - if !wasmi_fuel.is_zero() { - self.engine_consumed = wasmi_fuel_total; - let reftime_consumed = - wasmi_fuel.saturating_mul(T::Schedule::get().instruction_weights.base as u64); - let ref_time_left = self - .gas_left - .ref_time() - .checked_sub(reftime_consumed) - .ok_or_else(|| Error::::OutOfGas)?; - - *(self.gas_left.ref_time_mut()) = ref_time_left; - } - Ok(self.gas_left) + /// It is important that this does **not** actually sync with the executor. That has + /// to be done by the caller. + pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result { + let chargable_executor_resource = before + .0 + .saturating_sub(self.gas_left().ref_time()) + .checked_div(u64::from(T::Schedule::get().instruction_weights.base)) + .ok_or(Error::::InvalidSchedule)?; + self.executor_consumed.saturating_accrue(chargable_executor_resource); + Ok(Syncable(chargable_executor_resource)) } /// Returns the amount of gas that is required to run the same call. diff --git a/substrate/frame/contracts/src/lib.rs b/substrate/frame/contracts/src/lib.rs index 533085f2b874f6cc1d6c122fa6db5c8498c3db0c..d8939ee88baf9a610d29394bf0c6334a591ef3d6 100644 --- a/substrate/frame/contracts/src/lib.rs +++ b/substrate/frame/contracts/src/lib.rs @@ -214,6 +214,18 @@ pub struct Environment { block_number: EnvironmentType>, } +/// Defines the current version of the HostFn APIs. +/// This is used to communicate the available APIs in pallet-contracts. +/// +/// The version is bumped any time a new HostFn is added or stabilized. +#[derive(Encode, Decode, TypeInfo)] +pub struct ApiVersion(u16); +impl Default for ApiVersion { + fn default() -> Self { + Self(1) + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -325,7 +337,7 @@ pub mod pallet { type DepositPerItem: Get>; /// The percentage of the storage deposit that should be held for using a code hash. - /// Instantiating a contract, or calling [`chain_extension::Ext::add_delegate_dependency`] + /// Instantiating a contract, or calling [`chain_extension::Ext::lock_delegate_dependency`] /// protects the code from being removed. In order to prevent abuse these actions are /// protected with a percentage of the code deposit. #[pallet::constant] @@ -347,7 +359,7 @@ pub mod pallet { type MaxStorageKeyLen: Get; /// The maximum number of delegate_dependencies that a contract can lock with - /// [`chain_extension::Ext::add_delegate_dependency`]. + /// [`chain_extension::Ext::lock_delegate_dependency`]. #[pallet::constant] type MaxDelegateDependencies: Get; @@ -402,6 +414,12 @@ pub mod pallet { #[pallet::constant] type Environment: Get>; + /// The version of the HostFn APIs that are available in the runtime. + /// + /// Only valid value is `()`. + #[pallet::constant] + type ApiVersion: Get; + /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and /// execute XCM programs. type Xcm: xcm_builder::Controller< diff --git a/substrate/frame/contracts/src/migration/v10.rs b/substrate/frame/contracts/src/migration/v10.rs index 22fad38739e751c9b68dfb3151751e08331f5218..d64673aac7d268df80adb955ff3d5c5fd16c5681 100644 --- a/substrate/frame/contracts/src/migration/v10.rs +++ b/substrate/frame/contracts/src/migration/v10.rs @@ -25,7 +25,10 @@ use crate::{ CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET, }; use codec::{Decode, Encode}; -use core::cmp::{max, min}; +use core::{ + cmp::{max, min}, + ops::Deref, +}; use frame_support::{ pallet_prelude::*, storage_alias, @@ -42,7 +45,7 @@ use sp_runtime::{ traits::{Hash, TrailingZeroInput, Zero}, Perbill, Saturating, }; -use sp_std::{ops::Deref, prelude::*}; +use sp_std::prelude::*; mod old { use super::*; diff --git a/substrate/frame/contracts/src/schedule.rs b/substrate/frame/contracts/src/schedule.rs index 51c7c0bd9dd1f0715fb1c5f9d78f4d9c6cfa1fb6..b2e3801deaec1a36aef589427a6bf9dd324183b8 100644 --- a/substrate/frame/contracts/src/schedule.rs +++ b/substrate/frame/contracts/src/schedule.rs @@ -21,11 +21,11 @@ use crate::{weights::WeightInfo, Config}; use codec::{Decode, Encode}; +use core::marker::PhantomData; use frame_support::{weights::Weight, DefaultNoBound}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_std::marker::PhantomData; /// Definition of the cost schedule and other parameterizations for the wasm vm. /// @@ -39,11 +39,7 @@ use sp_std::marker::PhantomData; /// fn create_schedule() -> Schedule { /// Schedule { /// limits: Limits { -/// globals: 3, -/// parameters: 3, /// memory_pages: 16, -/// table_size: 3, -/// br_table_size: 3, /// .. Default::default() /// }, /// instruction_weights: InstructionWeights { @@ -77,38 +73,9 @@ pub struct Limits { /// The maximum number of topics supported by an event. pub event_topics: u32, - /// Maximum number of globals a module is allowed to declare. - /// - /// Globals are not limited through the linear memory limit `memory_pages`. - 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 - /// the stack height instrumentation: The costs of executing the stack height - /// instrumentation for an indirectly called function scales linearly with the amount - /// of parameters of this function. Because the stack height instrumentation itself is - /// is not weight metered its costs must be static (via this limit) and included in - /// the costs of the instructions that cause them (call, call_indirect). - pub parameters: u32, - /// Maximum number of memory pages allowed for a contract. pub memory_pages: u32, - /// Maximum number of elements allowed in a table. - /// - /// Currently, the only type of element that is allowed in a table is funcref. - pub table_size: u32, - - /// Maximum number of elements that can appear as immediate value to the br_table instruction. - pub br_table_size: u32, - /// The maximum length of a subject in bytes used for PRNG generation. pub subject_len: u32, @@ -331,11 +298,11 @@ pub struct HostFnWeights { /// Weight of calling `instantiation_nonce`. pub instantiation_nonce: Weight, - /// Weight of calling `add_delegate_dependency`. - pub add_delegate_dependency: Weight, + /// Weight of calling `lock_delegate_dependency`. + pub lock_delegate_dependency: Weight, - /// Weight of calling `remove_delegate_dependency`. - pub remove_delegate_dependency: Weight, + /// Weight of calling `unlock_delegate_dependency`. + pub unlock_delegate_dependency: Weight, /// The type parameter is used in the default implementation. #[codec(skip)] @@ -370,13 +337,7 @@ impl Default for Limits { fn default() -> Self { Self { event_topics: 4, - globals: 256, - locals: 1024, - parameters: 128, memory_pages: 16, - // 4k function pointers (This is in count not bytes). - table_size: 4096, - br_table_size: 256, subject_len: 32, payload_len: 16 * 1024, runtime_memory: 1024 * 1024 * 128, @@ -474,8 +435,8 @@ impl Default for HostFnWeights { reentrance_count: cost!(seal_reentrance_count), account_reentrance_count: cost!(seal_account_reentrance_count), instantiation_nonce: cost!(seal_instantiation_nonce), - add_delegate_dependency: cost!(add_delegate_dependency), - remove_delegate_dependency: cost!(remove_delegate_dependency), + lock_delegate_dependency: cost!(lock_delegate_dependency), + unlock_delegate_dependency: cost!(unlock_delegate_dependency), _phantom: PhantomData, } } diff --git a/substrate/frame/contracts/src/storage.rs b/substrate/frame/contracts/src/storage.rs index d4d261f4ec1ffcfc68c5245864b00ea2f47928ed..3304166607d28af88ebd381e46bfd18f3c3b293c 100644 --- a/substrate/frame/contracts/src/storage.rs +++ b/substrate/frame/contracts/src/storage.rs @@ -66,7 +66,7 @@ pub struct ContractInfo { storage_base_deposit: BalanceOf, /// Map of code hashes and deposit balances. /// - /// Tracks the code hash and deposit held for adding delegate dependencies. Dependencies added + /// Tracks the code hash and deposit held for locking delegate dependencies. Dependencies added /// to the map can not be removed from the chain state and can be safely used for delegate /// calls. delegate_dependencies: BoundedBTreeMap, BalanceOf, T::MaxDelegateDependencies>, @@ -233,7 +233,7 @@ impl ContractInfo { /// /// Returns an error if the maximum number of delegate_dependencies is reached or if /// the delegate dependency already exists. - pub fn add_delegate_dependency( + pub fn lock_delegate_dependency( &mut self, code_hash: CodeHash, amount: BalanceOf, @@ -249,7 +249,7 @@ impl ContractInfo { /// dependency. /// /// Returns an error if the entry doesn't exist. - pub fn remove_delegate_dependency( + pub fn unlock_delegate_dependency( &mut self, code_hash: &CodeHash, ) -> Result, DispatchError> { diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index 5711d3ccc83a527fae49f4ea6397a5a4b5fe54a9..2d1c5b315a34180cd1c007a2d7ebdc5bb6d0b705 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -35,12 +35,12 @@ use crate::{ tests::test_utils::{get_contract, get_contract_checked}, wasm::{Determinism, ReturnErrorCode as RuntimeReturnCode}, weights::WeightInfo, - BalanceOf, Code, CodeHash, CodeInfoOf, CollectEvents, Config, ContractInfo, ContractInfoOf, - DebugInfo, DefaultAddressGenerator, DeletionQueueCounter, Error, HoldReason, + Array, BalanceOf, Code, CodeHash, CodeInfoOf, CollectEvents, Config, ContractInfo, + ContractInfoOf, DebugInfo, DefaultAddressGenerator, DeletionQueueCounter, Error, HoldReason, MigrationInProgress, Origin, Pallet, PristineCode, Schedule, }; use assert_matches::assert_matches; -use codec::Encode; +use codec::{Decode, Encode}; use frame_support::{ assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_noop, assert_ok, derive_impl, @@ -217,8 +217,6 @@ impl ChainExtension for TestExtension { where E: Ext, { - use codec::Decode; - let func_id = env.func_id(); let id = env.ext_id() as u32 | func_id as u32; match func_id { @@ -334,29 +332,10 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; type AccountId = AccountId32; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_insecure_randomness_collective_flip::Config for Test {} impl pallet_balances::Config for Test { @@ -486,6 +465,7 @@ impl Config for Test { type MaxDelegateDependencies = MaxDelegateDependencies; type Debug = TestDebug; type Environment = (); + type ApiVersion = (); type Xcm = (); } @@ -2924,12 +2904,13 @@ fn debug_message_invalid_utf8() { } #[test] -fn gas_estimation_nested_call_fixed_limit() { +fn gas_estimation_for_subcalls() { let (caller_code, _caller_hash) = compile_module::("call_with_limit").unwrap(); - let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); + let (call_runtime_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + let (dummy_code, _callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); + let _ = ::Currency::set_balance(&ALICE, 2_000 * min_balance); let addr_caller = Contracts::bare_instantiate( ALICE, @@ -2938,7 +2919,7 @@ fn gas_estimation_nested_call_fixed_limit() { None, Code::Upload(caller_code), vec![], - vec![0], + vec![], DebugInfo::Skip, CollectEvents::Skip, ) @@ -2946,14 +2927,14 @@ fn gas_estimation_nested_call_fixed_limit() { .unwrap() .account_id; - let addr_callee = Contracts::bare_instantiate( + let addr_dummy = Contracts::bare_instantiate( ALICE, min_balance * 100, GAS_LIMIT, None, - Code::Upload(callee_code), + Code::Upload(dummy_code), + vec![], vec![], - vec![1], DebugInfo::Skip, CollectEvents::Skip, ) @@ -2961,68 +2942,136 @@ fn gas_estimation_nested_call_fixed_limit() { .unwrap() .account_id; - let input: Vec = AsRef::<[u8]>::as_ref(&addr_callee) - .iter() - .cloned() - .chain((GAS_LIMIT / 5).ref_time().to_le_bytes()) - .chain((GAS_LIMIT / 5).proof_size().to_le_bytes()) - .collect(); - - // Call in order to determine the gas that is required for this call - let result = Contracts::bare_call( + let addr_call_runtime = Contracts::bare_instantiate( ALICE, - addr_caller.clone(), - 0, + min_balance * 100, GAS_LIMIT, None, - input.clone(), + Code::Upload(call_runtime_code), + vec![], + vec![], DebugInfo::Skip, CollectEvents::Skip, - Determinism::Enforced, - ); - assert_ok!(&result.result); + ) + .result + .unwrap() + .account_id; - // We have a subcall with a fixed gas limit. This constitutes precharging. - assert!(result.gas_required.all_gt(result.gas_consumed)); + // Run the test for all of those weight limits for the subcall + let weights = [ + Weight::zero(), + GAS_LIMIT, + GAS_LIMIT * 2, + GAS_LIMIT / 5, + Weight::from_parts(0, GAS_LIMIT.proof_size()), + Weight::from_parts(GAS_LIMIT.ref_time(), 0), + ]; - // Make the same call using the estimated gas. Should succeed. - assert_ok!( - Contracts::bare_call( - ALICE, - addr_caller.clone(), - 0, - result.gas_required, - Some(result.storage_deposit.charge_or_zero()), - input.clone(), - DebugInfo::Skip, - CollectEvents::Skip, - Determinism::Enforced, - ) - .result - ); + // This call is passed to the sub call in order to create a large `required_weight` + let runtime_call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { + pre_charge: Weight::from_parts(10_000_000_000, 512 * 1024), + actual_weight: Weight::from_parts(1, 1), + }) + .encode(); - // Make the same call using proof_size but less than estimated. Should fail with OutOfGas. - let result = Contracts::bare_call( - ALICE, - addr_caller, - 0, - result.gas_required.sub_proof_size(1), - Some(result.storage_deposit.charge_or_zero()), - input, - DebugInfo::Skip, - CollectEvents::Skip, - Determinism::Enforced, - ) - .result; - assert_err!(result, >::OutOfGas); + // Encodes which contract should be sub called with which input + let sub_calls: [(&[u8], Vec<_>, bool); 2] = [ + (addr_dummy.as_ref(), vec![], false), + (addr_call_runtime.as_ref(), runtime_call, true), + ]; + + for weight in weights { + for (sub_addr, sub_input, out_of_gas_in_subcall) in &sub_calls { + let input: Vec = sub_addr + .iter() + .cloned() + .chain(weight.ref_time().to_le_bytes()) + .chain(weight.proof_size().to_le_bytes()) + .chain(sub_input.clone()) + .collect(); + + // Call in order to determine the gas that is required for this call + let result = Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + GAS_LIMIT, + None, + input.clone(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ); + assert_ok!(&result.result); + + // If the out of gas happens in the subcall the caller contract + // will just trap. Otherwise we would need to forward an error + // code to signal that the sub contract ran out of gas. + let error: DispatchError = if *out_of_gas_in_subcall { + assert!(result.gas_required.all_gt(result.gas_consumed)); + >::ContractTrapped.into() + } else { + assert_eq!(result.gas_required, result.gas_consumed); + >::OutOfGas.into() + }; + + // Make the same call using the estimated gas. Should succeed. + assert_ok!( + Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + result.gas_required, + Some(result.storage_deposit.charge_or_zero()), + input.clone(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result + ); + + // Check that it fails with too little ref_time + assert_err!( + Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + result.gas_required.sub_ref_time(1), + Some(result.storage_deposit.charge_or_zero()), + input.clone(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result, + error, + ); + + // Check that it fails with too little proof_size + assert_err!( + Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + result.gas_required.sub_proof_size(1), + Some(result.storage_deposit.charge_or_zero()), + input, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result, + error, + ); + } + } }); } #[test] fn gas_estimation_call_runtime() { - use codec::Decode; let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); - let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = Contracts::min_balance(); let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); @@ -3043,25 +3092,11 @@ fn gas_estimation_call_runtime() { .unwrap() .account_id; - Contracts::bare_instantiate( - ALICE, - min_balance * 100, - GAS_LIMIT, - None, - Code::Upload(callee_code), - vec![], - vec![1], - DebugInfo::Skip, - CollectEvents::Skip, - ) - .result - .unwrap(); - // Call something trivial with a huge gas limit so that we can observe the effects // of pre-charging. This should create a difference between consumed and required. let call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge { - pre_charge: Weight::from_parts(10_000_000, 0), - actual_weight: Weight::from_parts(100, 0), + pre_charge: Weight::from_parts(10_000_000, 1_000), + actual_weight: Weight::from_parts(100, 100), }); let result = Contracts::bare_call( ALICE, @@ -3077,7 +3112,7 @@ fn gas_estimation_call_runtime() { // contract encodes the result of the dispatch runtime let outcome = u32::decode(&mut result.result.unwrap().data.as_ref()).unwrap(); assert_eq!(outcome, 0); - assert!(result.gas_required.ref_time() > result.gas_consumed.ref_time()); + assert!(result.gas_required.all_gt(result.gas_consumed)); // Make the same call using the required gas. Should succeed. assert_ok!( @@ -5372,21 +5407,21 @@ fn delegate_call_indeterministic_code() { } #[test] -fn add_remove_delegate_dependency_works() { +fn locking_delegate_dependency_works() { // set hash lock up deposit to 30%, to test deposit calculation. CODE_HASH_LOCKUP_DEPOSIT_PERCENT.with(|c| *c.borrow_mut() = Perbill::from_percent(30)); MAX_DELEGATE_DEPENDENCIES.with(|c| *c.borrow_mut() = 1); let (wasm_caller, self_code_hash) = - compile_module::("add_remove_delegate_dependency").unwrap(); + compile_module::("locking_delegate_dependency").unwrap(); let (wasm_callee, code_hash) = compile_module::("dummy").unwrap(); let (wasm_other, other_code_hash) = compile_module::("call").unwrap(); - // Define inputs with various actions to test adding / removing delegate_dependencies. + // Define inputs with various actions to test locking / unlocking delegate_dependencies. // See the contract for more details. let noop_input = (0u32, code_hash); - let add_delegate_dependency_input = (1u32, code_hash); - let remove_delegate_dependency_input = (2u32, code_hash); + let lock_delegate_dependency_input = (1u32, code_hash); + let unlock_delegate_dependency_input = (2u32, code_hash); let terminate_input = (3u32, code_hash); // Instantiate the caller contract with the given input. @@ -5423,9 +5458,9 @@ fn add_remove_delegate_dependency_works() { ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { let _ = Balances::set_balance(&ALICE, 1_000_000); - // Instantiate with add_delegate_dependency should fail since the code is not yet on chain. + // Instantiate with lock_delegate_dependency should fail since the code is not yet on chain. assert_err!( - instantiate(&add_delegate_dependency_input).result, + instantiate(&lock_delegate_dependency_input).result, Error::::CodeNotFound ); @@ -5435,7 +5470,7 @@ fn add_remove_delegate_dependency_works() { .unwrap(); // Instantiate should now work. - let addr_caller = instantiate(&add_delegate_dependency_input).result.unwrap().account_id; + let addr_caller = instantiate(&lock_delegate_dependency_input).result.unwrap().account_id; // There should be a dependency and a deposit. let contract = test_utils::get_contract(&addr_caller); @@ -5456,27 +5491,27 @@ fn add_remove_delegate_dependency_works() { >::CodeInUse ); - // Adding an already existing dependency should fail. + // Locking an already existing dependency should fail. assert_err!( - call(&addr_caller, &add_delegate_dependency_input).result, + call(&addr_caller, &lock_delegate_dependency_input).result, Error::::DelegateDependencyAlreadyExists ); - // Adding a dependency to self should fail. + // Locking self should fail. assert_err!( call(&addr_caller, &(1u32, self_code_hash)).result, Error::::CannotAddSelfAsDelegateDependency ); - // Adding more than the maximum allowed delegate_dependencies should fail. + // Locking more than the maximum allowed delegate_dependencies should fail. Contracts::bare_upload_code(ALICE, wasm_other, None, Determinism::Enforced).unwrap(); assert_err!( call(&addr_caller, &(1u32, other_code_hash)).result, Error::::MaxDelegateDependenciesReached ); - // Removing dependency should work. - assert_ok!(call(&addr_caller, &remove_delegate_dependency_input).result); + // Unlocking dependency should work. + assert_ok!(call(&addr_caller, &unlock_delegate_dependency_input).result); // Dependency should be removed, and deposit should be returned. let contract = test_utils::get_contract(&addr_caller); @@ -5491,18 +5526,18 @@ fn add_remove_delegate_dependency_works() { // Removing an unexisting dependency should fail. assert_err!( - call(&addr_caller, &remove_delegate_dependency_input).result, + call(&addr_caller, &unlock_delegate_dependency_input).result, Error::::DelegateDependencyNotFound ); - // Adding a dependency with a storage limit too low should fail. + // Locking a dependency with a storage limit too low should fail. DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = dependency_deposit - 1); assert_err!( - call(&addr_caller, &add_delegate_dependency_input).result, + call(&addr_caller, &lock_delegate_dependency_input).result, Error::::StorageDepositLimitExhausted ); - // Since we removed the dependency we should now be able to remove the code. + // Since we unlocked the dependency we should now be able to remove the code. assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); // Calling should fail since the delegated contract is not on chain anymore. @@ -5511,7 +5546,7 @@ fn add_remove_delegate_dependency_works() { // Restore initial deposit limit and add the dependency back. DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = 10_000_000); Contracts::bare_upload_code(ALICE, wasm_callee, None, Determinism::Enforced).unwrap(); - call(&addr_caller, &add_delegate_dependency_input).result.unwrap(); + call(&addr_caller, &lock_delegate_dependency_input).result.unwrap(); // Call terminate should work, and return the deposit. let balance_before = test_utils::get_balance(&ALICE); @@ -5943,3 +5978,53 @@ fn balance_api_returns_free_balance() { ); }); } + +#[test] +fn gas_consumed_is_linear_for_nested_calls() { + let (code, _code_hash) = compile_module::("recurse").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(code), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + let max_call_depth = ::CallStack::size() as u32; + let [gas_0, gas_1, gas_2, gas_max] = { + [0u32, 1u32, 2u32, max_call_depth] + .iter() + .map(|i| { + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + i.encode(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ); + assert_ok!(result.result); + result.gas_consumed + }) + .collect::>() + .try_into() + .unwrap() + }; + + let gas_per_recursion = gas_2.checked_sub(&gas_1).unwrap(); + assert_eq!(gas_max, gas_0 + gas_per_recursion * max_call_depth as u64); + }); +} diff --git a/substrate/frame/contracts/src/wasm/mod.rs b/substrate/frame/contracts/src/wasm/mod.rs index d00ec2e47521b12c8082b5a0fd2001fbf5562948..c96c28565095592b4609e63b5701d91df2e04786 100644 --- a/substrate/frame/contracts/src/wasm/mod.rs +++ b/substrate/frame/contracts/src/wasm/mod.rs @@ -386,7 +386,7 @@ impl Executable for WasmBlob { let engine_consumed_total = store.fuel_consumed().expect("Fuel metering is enabled; qed"); let gas_meter = store.data_mut().ext().gas_meter_mut(); - gas_meter.charge_fuel(engine_consumed_total)?; + let _ = gas_meter.sync_from_executor(engine_consumed_total)?; store.into_data().to_execution_result(result) }; @@ -687,6 +687,10 @@ mod tests { &mut self.gas_meter } fn charge_storage(&mut self, _diff: &crate::storage::meter::Diff) {} + + fn debug_buffer_enabled(&self) -> bool { + true + } fn append_debug_buffer(&mut self, msg: &str) -> bool { self.debug_buffer.extend(msg.as_bytes()); true @@ -729,14 +733,14 @@ mod tests { Ok(()) } fn decrement_refcount(_code_hash: CodeHash) {} - fn add_delegate_dependency( + fn lock_delegate_dependency( &mut self, code: CodeHash, ) -> Result<(), DispatchError> { self.delegate_dependencies.borrow_mut().insert(code); Ok(()) } - fn remove_delegate_dependency( + fn unlock_delegate_dependency( &mut self, code: &CodeHash, ) -> Result<(), DispatchError> { @@ -3395,16 +3399,16 @@ mod tests { } #[test] - fn add_remove_delegate_dependency() { - const CODE_ADD_REMOVE_DELEGATE_DEPENDENCY: &str = r#" + fn lock_unlock_delegate_dependency() { + const CODE_LOCK_UNLOCK_DELEGATE_DEPENDENCY: &str = r#" (module - (import "seal0" "add_delegate_dependency" (func $add_delegate_dependency (param i32))) - (import "seal0" "remove_delegate_dependency" (func $remove_delegate_dependency (param i32))) + (import "seal0" "lock_delegate_dependency" (func $lock_delegate_dependency (param i32))) + (import "seal0" "unlock_delegate_dependency" (func $unlock_delegate_dependency (param i32))) (import "env" "memory" (memory 1 1)) (func (export "call") - (call $add_delegate_dependency (i32.const 0)) - (call $add_delegate_dependency (i32.const 32)) - (call $remove_delegate_dependency (i32.const 32)) + (call $lock_delegate_dependency (i32.const 0)) + (call $lock_delegate_dependency (i32.const 32)) + (call $unlock_delegate_dependency (i32.const 32)) ) (func (export "deploy")) @@ -3422,7 +3426,7 @@ mod tests { ) "#; let mut mock_ext = MockExt::default(); - assert_ok!(execute(&CODE_ADD_REMOVE_DELEGATE_DEPENDENCY, vec![], &mut mock_ext)); + assert_ok!(execute(&CODE_LOCK_UNLOCK_DELEGATE_DEPENDENCY, vec![], &mut mock_ext)); let delegate_dependencies: Vec<_> = mock_ext.delegate_dependencies.into_inner().into_iter().collect(); assert_eq!(delegate_dependencies.len(), 1); diff --git a/substrate/frame/contracts/src/wasm/prepare.rs b/substrate/frame/contracts/src/wasm/prepare.rs index dfe8c4f8f9b91fe5e7d73032282fe50effd51d38..5cdf5600fcdc6d08b55cd68685a532494b4583a7 100644 --- a/substrate/frame/contracts/src/wasm/prepare.rs +++ b/substrate/frame/contracts/src/wasm/prepare.rs @@ -384,12 +384,7 @@ mod tests { let wasm = wat::parse_str($wat).unwrap().try_into().unwrap(); let schedule = Schedule { limits: Limits { - globals: 3, - locals: 3, - parameters: 3, memory_pages: 16, - table_size: 3, - br_table_size: 3, .. Default::default() }, .. Default::default() diff --git a/substrate/frame/contracts/src/wasm/runtime.rs b/substrate/frame/contracts/src/wasm/runtime.rs index 3e49b68f86b6c966c2a526f4124bc09771bc92d1..f440c818166d865a3b884eea03107c663a985046 100644 --- a/substrate/frame/contracts/src/wasm/runtime.rs +++ b/substrate/frame/contracts/src/wasm/runtime.rs @@ -21,7 +21,6 @@ use crate::{ exec::{ExecError, ExecResult, Ext, Key, TopicOf}, gas::{ChargedAmount, Token}, primitives::ExecReturnValue, - schedule::HostFnWeights, BalanceOf, CodeHash, Config, DebugBufferVec, Error, SENTINEL, }; use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; @@ -235,6 +234,8 @@ pub enum RuntimeCosts { ChainExtension(Weight), /// Weight charged for calling into the runtime. CallRuntime(Weight), + /// Weight charged for calling xcm_execute. + CallXcmExecute(Weight), /// Weight of calling `seal_set_code_hash` SetCodeHash, /// Weight of calling `ecdsa_to_eth_address` @@ -245,16 +246,24 @@ pub enum RuntimeCosts { AccountEntranceCount, /// Weight of calling `instantiation_nonce` InstantationNonce, - /// Weight of calling `add_delegate_dependency` - AddDelegateDependency, - /// Weight of calling `remove_delegate_dependency` - RemoveDelegateDependency, + /// Weight of calling `lock_delegate_dependency` + LockDelegateDependency, + /// Weight of calling `unlock_delegate_dependency` + UnlockDelegateDependency, } -impl RuntimeCosts { - fn token(&self, s: &HostFnWeights) -> RuntimeToken { +impl Token for RuntimeCosts { + fn influence_lowest_gas_limit(&self) -> bool { + match self { + &Self::CallXcmExecute(_) => false, + _ => true, + } + } + + fn weight(&self) -> Weight { + let s = T::Schedule::get().host_fn_weights; use self::RuntimeCosts::*; - let weight = match *self { + match *self { CopyFromContract(len) => s.return_per_byte.saturating_mul(len.into()), CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()), Caller => s.caller, @@ -323,20 +332,14 @@ impl RuntimeCosts { Sr25519Verify(len) => s .sr25519_verify .saturating_add(s.sr25519_verify_per_byte.saturating_mul(len.into())), - ChainExtension(weight) => weight, - CallRuntime(weight) => weight, + ChainExtension(weight) | CallRuntime(weight) | CallXcmExecute(weight) => weight, SetCodeHash => s.set_code_hash, EcdsaToEthAddress => s.ecdsa_to_eth_address, ReentrantCount => s.reentrance_count, AccountEntranceCount => s.account_reentrance_count, InstantationNonce => s.instantiation_nonce, - AddDelegateDependency => s.add_delegate_dependency, - RemoveDelegateDependency => s.remove_delegate_dependency, - }; - RuntimeToken { - #[cfg(test)] - _created_from: *self, - weight, + LockDelegateDependency => s.lock_delegate_dependency, + UnlockDelegateDependency => s.unlock_delegate_dependency, } } } @@ -347,25 +350,10 @@ impl RuntimeCosts { /// a function won't work out. macro_rules! charge_gas { ($runtime:expr, $costs:expr) => {{ - let token = $costs.token(&$runtime.ext.schedule().host_fn_weights); - $runtime.ext.gas_meter_mut().charge(token) + $runtime.ext.gas_meter_mut().charge($costs) }}; } -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Copy, Clone)] -struct RuntimeToken { - #[cfg(test)] - _created_from: RuntimeCosts, - weight: Weight, -} - -impl Token for RuntimeToken { - fn weight(&self) -> Weight { - self.weight - } -} - /// The kind of call that should be performed. enum CallType { /// Execute another instantiated contract @@ -506,28 +494,25 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { /// This is when a maximum a priori amount was charged and then should be partially /// refunded to match the actual amount. pub fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) { - let token = actual_costs.token(&self.ext.schedule().host_fn_weights); - self.ext.gas_meter_mut().adjust_gas(charged, token); + self.ext.gas_meter_mut().adjust_gas(charged, actual_costs); } /// Charge, Run and adjust gas, for executing the given dispatchable. - fn call_dispatchable< - ErrorReturnCode: Get, - F: FnOnce(&mut Self) -> DispatchResultWithPostInfo, - >( + fn call_dispatchable>( &mut self, dispatch_info: DispatchInfo, - run: F, + runtime_cost: impl Fn(Weight) -> RuntimeCosts, + run: impl FnOnce(&mut Self) -> DispatchResultWithPostInfo, ) -> Result { use frame_support::dispatch::extract_actual_weight; - let charged = self.charge_gas(RuntimeCosts::CallRuntime(dispatch_info.weight))?; + let charged = self.charge_gas(runtime_cost(dispatch_info.weight))?; let result = run(self); let actual_weight = extract_actual_weight(&result, &dispatch_info); - self.adjust_gas(charged, RuntimeCosts::CallRuntime(actual_weight)); + self.adjust_gas(charged, runtime_cost(actual_weight)); match result { Ok(_) => Ok(ReturnErrorCode::Success), Err(e) => { - if self.ext.append_debug_buffer("") { + if self.ext.debug_buffer_enabled() { self.ext.append_debug_buffer("call failed with: "); self.ext.append_debug_buffer(e.into()); }; @@ -1231,7 +1216,6 @@ pub mod env { /// Make a call to another contract. /// See [`pallet_contracts_uapi::HostFn::call_v2`]. #[version(2)] - #[unstable] fn call( ctx: _, memory: _, @@ -1368,7 +1352,6 @@ pub mod env { /// Instantiate a contract with the specified code hash. /// See [`pallet_contracts_uapi::HostFn::instantiate_v2`]. #[version(2)] - #[unstable] fn instantiate( ctx: _, memory: _, @@ -1542,7 +1525,6 @@ pub mod env { /// Checks whether the caller of the current contract is root. /// See [`pallet_contracts_uapi::HostFn::caller_is_root`]. - #[unstable] fn caller_is_root(ctx: _, _memory: _) -> Result { ctx.charge_gas(RuntimeCosts::CallerIsRoot)?; Ok(ctx.ext.caller_is_root() as u32) @@ -2114,9 +2096,11 @@ pub mod env { ctx.charge_gas(RuntimeCosts::CopyFromContract(call_len))?; let call: ::RuntimeCall = ctx.read_sandbox_memory_as_unbounded(memory, call_ptr, call_len)?; - ctx.call_dispatchable::(call.get_dispatch_info(), |ctx| { - ctx.ext.call_runtime(call) - }) + ctx.call_dispatchable::( + call.get_dispatch_info(), + RuntimeCosts::CallRuntime, + |ctx| ctx.ext.call_runtime(call), + ) } /// Execute an XCM program locally, using the contract's address as the origin. @@ -2127,7 +2111,6 @@ pub mod env { memory: _, msg_ptr: u32, msg_len: u32, - output_ptr: u32, ) -> Result { use frame_support::dispatch::DispatchInfo; use xcm::VersionedXcm; @@ -2136,26 +2119,27 @@ pub mod env { ctx.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; let message: VersionedXcm> = ctx.read_sandbox_memory_as_unbounded(memory, msg_ptr, msg_len)?; + ensure_executable::(&message)?; let execute_weight = <::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); let weight = ctx.ext.gas_meter().gas_left().max(execute_weight); let dispatch_info = DispatchInfo { weight, ..Default::default() }; - ensure_executable::(&message)?; - ctx.call_dispatchable::(dispatch_info, |ctx| { - let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into(); - let outcome = <::Xcm>::execute( - origin, - Box::new(message), - weight.saturating_sub(execute_weight), - )?; + ctx.call_dispatchable::( + dispatch_info, + RuntimeCosts::CallXcmExecute, + |ctx| { + let origin = crate::RawOrigin::Signed(ctx.ext.address().clone()).into(); + let weight_used = <::Xcm>::execute( + origin, + Box::new(message), + weight.saturating_sub(execute_weight), + )?; - ctx.write_sandbox_memory(memory, output_ptr, &outcome.encode())?; - let pre_dispatch_weight = - <::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); - Ok(Some(outcome.weight_used().saturating_add(pre_dispatch_weight)).into()) - }) + Ok(Some(weight_used.saturating_add(execute_weight)).into()) + }, + ) } /// Send an XCM program from the contract to the specified destination. @@ -2320,22 +2304,22 @@ pub mod env { } /// Adds a new delegate dependency to the contract. - /// See [`pallet_contracts_uapi::HostFn::add_delegate_dependency`]. + /// See [`pallet_contracts_uapi::HostFn::lock_delegate_dependency`]. #[unstable] - fn add_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> { - ctx.charge_gas(RuntimeCosts::AddDelegateDependency)?; + fn lock_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> { + ctx.charge_gas(RuntimeCosts::LockDelegateDependency)?; let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?; - ctx.ext.add_delegate_dependency(code_hash)?; + ctx.ext.lock_delegate_dependency(code_hash)?; Ok(()) } /// Removes the delegate dependency from the contract. - /// see [`pallet_contracts_uapi::HostFn::remove_delegate_dependency`]. + /// see [`pallet_contracts_uapi::HostFn::unlock_delegate_dependency`]. #[unstable] - fn remove_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> { - ctx.charge_gas(RuntimeCosts::RemoveDelegateDependency)?; + fn unlock_delegate_dependency(ctx: _, memory: _, code_hash_ptr: u32) -> Result<(), TrapReason> { + ctx.charge_gas(RuntimeCosts::UnlockDelegateDependency)?; let code_hash = ctx.read_sandbox_memory_as(memory, code_hash_ptr)?; - ctx.ext.remove_delegate_dependency(&code_hash)?; + ctx.ext.unlock_delegate_dependency(&code_hash)?; Ok(()) } } diff --git a/substrate/frame/contracts/src/weights.rs b/substrate/frame/contracts/src/weights.rs index fa9df922a7cbb9711c7b1315cf680a30fc4c5ebb..962591290b3149d6b72ea737871b98f38f5c07a3 100644 --- a/substrate/frame/contracts/src/weights.rs +++ b/substrate/frame/contracts/src/weights.rs @@ -124,8 +124,8 @@ 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 add_delegate_dependency(r: u32, ) -> Weight; - fn remove_delegate_dependency(r: u32, ) -> Weight; + fn lock_delegate_dependency(r: u32, ) -> Weight; + fn unlock_delegate_dependency(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; @@ -1891,7 +1891,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[0, 32]`. - fn add_delegate_dependency(r: u32, ) -> Weight { + fn lock_delegate_dependency(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `928 + r * (131 ±0)` // Estimated: `6878 + r * (2606 ±0)` @@ -1920,7 +1920,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[0, 32]`. - fn remove_delegate_dependency(r: u32, ) -> Weight { + fn unlock_delegate_dependency(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `969 + r * (183 ±0)` // Estimated: `129453 + r * (2568 ±0)` @@ -3787,7 +3787,7 @@ impl WeightInfo for () { /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[0, 32]`. - fn add_delegate_dependency(r: u32, ) -> Weight { + fn lock_delegate_dependency(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `928 + r * (131 ±0)` // Estimated: `6878 + r * (2606 ±0)` @@ -3816,7 +3816,7 @@ impl WeightInfo for () { /// Storage: `System::EventTopics` (r:2 w:2) /// Proof: `System::EventTopics` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `r` is `[0, 32]`. - fn remove_delegate_dependency(r: u32, ) -> Weight { + fn unlock_delegate_dependency(r: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `969 + r * (183 ±0)` // Estimated: `129453 + r * (2568 ±0)` diff --git a/substrate/frame/contracts/uapi/Cargo.toml b/substrate/frame/contracts/uapi/Cargo.toml index eb8a9c61820922ce51d4ece5ac54609e0d4fe435..a5081af2a2d280cc1956dcf2dbb69de18588ccd4 100644 --- a/substrate/frame/contracts/uapi/Cargo.toml +++ b/substrate/frame/contracts/uapi/Cargo.toml @@ -23,6 +23,9 @@ scale = { package = "parity-scale-codec", version = "3.6.1", default-features = [target.'cfg(target_arch = "riscv32")'.dependencies] polkavm-derive = '0.5.0' +[package.metadata.docs.rs] +default-target = ["wasm32-unknown-unknown"] + [features] default = ["scale"] scale = ["dep:scale", "scale-info"] diff --git a/substrate/frame/contracts/uapi/src/host.rs b/substrate/frame/contracts/uapi/src/host.rs index 21350bc4eb3e42ffbdfd8b951cd66fdffbd5360e..c25be4479cefaac81e8cb25c6b56502f9e2fd83a 100644 --- a/substrate/frame/contracts/uapi/src/host.rs +++ b/substrate/frame/contracts/uapi/src/host.rs @@ -93,7 +93,7 @@ pub trait HostFn { /// - `output`: A reference to the output data buffer to write the address. fn address(output: &mut &mut [u8]); - /// Adds a new delegate dependency to the contract. + /// Lock a new delegate dependency to the contract. /// /// Traps if the maximum number of delegate_dependencies is reached or if /// the delegate dependency already exists. @@ -102,10 +102,7 @@ pub trait HostFn { /// /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps /// otherwise. - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] - fn add_delegate_dependency(code_hash: &[u8]); + fn lock_delegate_dependency(code_hash: &[u8]); /// Stores the *free* balance of the current account into the supplied buffer. /// @@ -142,6 +139,7 @@ pub trait HostFn { /// /// Equivalent to the newer [`Self::call_v2`] version but works with /// *ref_time* Weight only + #[deprecated(note = "Deprecated, use newer version instead")] fn call_v1( flags: CallFlags, callee: &[u8], @@ -178,9 +176,6 @@ pub trait HostFn { /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] /// - [NotCallable][`crate::ReturnErrorCode::NotCallable] - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] fn call_v2( flags: CallFlags, callee: &[u8], @@ -277,9 +272,6 @@ pub trait HostFn { /// /// A return value of `true` indicates that this contract is being called by a root origin, /// and `false` indicates that the caller is a signed origin. - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] fn caller_is_root() -> u32; /// Clear the value at the given key in the contract storage. @@ -483,6 +475,7 @@ pub trait HostFn { /// /// Equivalent to the newer [`Self::instantiate_v2`] version but works /// with *ref_time* Weight only. + #[deprecated(note = "Deprecated, use newer version instead")] fn instantiate_v1( code_hash: &[u8], gas: u64, @@ -527,9 +520,6 @@ pub trait HostFn { /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] fn instantiate_v2( code_hash: &[u8], ref_time_limit: u64, @@ -605,10 +595,7 @@ pub trait HostFn { /// /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps /// otherwise. - #[deprecated( - note = "Unstable function. Behaviour can change without further notice. Use only for testing." - )] - fn remove_delegate_dependency(code_hash: &[u8]); + fn unlock_delegate_dependency(code_hash: &[u8]); /// Cease contract execution and save a data buffer as a result of the execution. /// @@ -795,7 +782,7 @@ pub trait HostFn { #[deprecated( note = "Unstable function. Behaviour can change without further notice. Use only for testing." )] - fn xcm_execute(msg: &[u8], output: &mut &mut [u8]) -> Result; + fn xcm_execute(msg: &[u8]) -> Result; /// Send an XCM program from the contract to the specified destination. /// This is equivalent to dispatching `pallet_xcm::send` through `call_runtime`, except that @@ -807,7 +794,6 @@ pub trait HostFn { /// traps otherwise. /// - `msg`: The message, should be decodable as a [VersionedXcm](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/enum.VersionedXcm.html), /// traps otherwise. - /// - `output`: A reference to the output data buffer to write the [XcmHash](https://paritytech.github.io/polkadot-sdk/master/staging_xcm/v3/type.XcmHash.html) /// /// # Return /// diff --git a/substrate/frame/contracts/uapi/src/host/riscv32.rs b/substrate/frame/contracts/uapi/src/host/riscv32.rs index b1934cc469e4b5cf92d8bb861bbbe073415b1789..dbd5abc42409741af2679d99274d1cdf48f2c757 100644 --- a/substrate/frame/contracts/uapi/src/host/riscv32.rs +++ b/substrate/frame/contracts/uapi/src/host/riscv32.rs @@ -281,11 +281,11 @@ impl HostFn for HostFnImpl { todo!() } - fn add_delegate_dependency(code_hash: &[u8]) { + fn lock_delegate_dependency(code_hash: &[u8]) { todo!() } - fn remove_delegate_dependency(code_hash: &[u8]) { + fn unlock_delegate_dependency(code_hash: &[u8]) { todo!() } @@ -297,7 +297,7 @@ impl HostFn for HostFnImpl { todo!() } - fn xcm_execute(msg: &[u8], output: &mut &mut [u8]) -> Result { + fn xcm_execute(msg: &[u8]) -> Result { todo!() } diff --git a/substrate/frame/contracts/uapi/src/host/wasm32.rs b/substrate/frame/contracts/uapi/src/host/wasm32.rs index 77cf22891e2f5e325aeed102748603b6b3cb1143..9651aa73d6f9bd3364432ed9135e460f62aa0b6c 100644 --- a/substrate/frame/contracts/uapi/src/host/wasm32.rs +++ b/substrate/frame/contracts/uapi/src/host/wasm32.rs @@ -23,7 +23,7 @@ mod sys { extern "C" { pub fn account_reentrance_count(account_ptr: *const u8) -> u32; - pub fn add_delegate_dependency(code_hash_ptr: *const u8); + pub fn lock_delegate_dependency(code_hash_ptr: *const u8); pub fn address(output_ptr: *mut u8, output_len_ptr: *mut u32); @@ -125,7 +125,7 @@ mod sys { pub fn reentrance_count() -> u32; - pub fn remove_delegate_dependency(code_hash_ptr: *const u8); + pub fn unlock_delegate_dependency(code_hash_ptr: *const u8); pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32) -> !; @@ -160,7 +160,7 @@ mod sys { pub fn weight_to_fee(gas: u64, output_ptr: *mut u8, output_len_ptr: *mut u32); - pub fn xcm_execute(msg_ptr: *const u8, msg_len: u32, output_ptr: *mut u8) -> ReturnCode; + pub fn xcm_execute(msg_ptr: *const u8, msg_len: u32) -> ReturnCode; pub fn xcm_send( dest_ptr: *const u8, @@ -803,12 +803,12 @@ impl HostFn for HostFnImpl { unsafe { sys::account_reentrance_count(account.as_ptr()) } } - fn add_delegate_dependency(code_hash: &[u8]) { - unsafe { sys::add_delegate_dependency(code_hash.as_ptr()) } + fn lock_delegate_dependency(code_hash: &[u8]) { + unsafe { sys::lock_delegate_dependency(code_hash.as_ptr()) } } - fn remove_delegate_dependency(code_hash: &[u8]) { - unsafe { sys::remove_delegate_dependency(code_hash.as_ptr()) } + fn unlock_delegate_dependency(code_hash: &[u8]) { + unsafe { sys::unlock_delegate_dependency(code_hash.as_ptr()) } } fn instantiation_nonce() -> u64 { @@ -819,9 +819,8 @@ impl HostFn for HostFnImpl { unsafe { sys::reentrance_count() } } - fn xcm_execute(msg: &[u8], output: &mut &mut [u8]) -> Result { - let ret_code = - unsafe { sys::xcm_execute(msg.as_ptr(), msg.len() as _, output.as_mut_ptr()) }; + fn xcm_execute(msg: &[u8]) -> Result { + let ret_code = unsafe { sys::xcm_execute(msg.as_ptr(), msg.len() as _) }; ret_code.into() } diff --git a/substrate/frame/conviction-voting/Cargo.toml b/substrate/frame/conviction-voting/Cargo.toml index 28ef8cd32fb2b31147b23b32383991e583e8b642..ff5af995026f39527780d964aea16ec13be1ec2b 100644 --- a/substrate/frame/conviction-voting/Cargo.toml +++ b/substrate/frame/conviction-voting/Cargo.toml @@ -22,7 +22,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "max-encoded-len", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/conviction-voting/src/lib.rs b/substrate/frame/conviction-voting/src/lib.rs index 1d6fbaa38694233a6766410728bf8efec0671d0e..466fc70a619b649e4b1aade0002e506dde430a1e 100644 --- a/substrate/frame/conviction-voting/src/lib.rs +++ b/substrate/frame/conviction-voting/src/lib.rs @@ -185,7 +185,7 @@ pub mod pallet { /// The account is already delegating. AlreadyDelegating, /// The account currently has votes attached to it and the operation cannot succeed until - /// these are removed, either through `unvote` or `reap_vote`. + /// these are removed through `remove_vote`. AlreadyVoting, /// Too high a balance was provided that the account cannot afford. InsufficientFunds, @@ -231,8 +231,8 @@ pub mod pallet { /// /// The dispatch origin of this call must be _Signed_, and the signing account must either: /// - be delegating already; or - /// - have no voting activity (if there is, then it will need to be removed/consolidated - /// through `reap_vote` or `unvote`). + /// - have no voting activity (if there is, then it will need to be removed through + /// `remove_vote`). /// /// - `to`: The account whose voting the `target` account's voting power will follow. /// - `class`: The class of polls to delegate. To delegate multiple classes, multiple calls diff --git a/substrate/frame/conviction-voting/src/tests.rs b/substrate/frame/conviction-voting/src/tests.rs index b67290e7fec59d5fcac665a578fec10f7586b4f1..dbcd643b60ff7c5ac2ec0a0a351650c69c200e37 100644 --- a/substrate/frame/conviction-voting/src/tests.rs +++ b/substrate/frame/conviction-voting/src/tests.rs @@ -23,11 +23,7 @@ use frame_support::{ assert_noop, assert_ok, derive_impl, parameter_types, traits::{ConstU32, ConstU64, Contains, Polling, VoteTally}, }; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; use super::*; use crate as pallet_conviction_voting; @@ -53,29 +49,8 @@ impl Contains for BaseFilter { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = BaseFilter; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/core-fellowship/Cargo.toml b/substrate/frame/core-fellowship/Cargo.toml index 8e59725d317443f14e6a2a0c78dbcb17d001f101..3e678d3274463f46619c19e71be97350fc1c5955 100644 --- a/substrate/frame/core-fellowship/Cargo.toml +++ b/substrate/frame/core-fellowship/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/core-fellowship/src/lib.rs b/substrate/frame/core-fellowship/src/lib.rs index e3924594321aa60c69efbdb6df61ee9f0b488205..d1b81c3ca134fbe30a14034164e45710cea12317 100644 --- a/substrate/frame/core-fellowship/src/lib.rs +++ b/substrate/frame/core-fellowship/src/lib.rs @@ -56,7 +56,6 @@ //! cannot be approved - they must proceed only to promotion prior to the offboard timeout elapsing. #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "128"] use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/substrate/frame/core-fellowship/src/tests/integration.rs b/substrate/frame/core-fellowship/src/tests/integration.rs index 57f9cad3dcb36335824577f2a850fbbb53013337..6f177ba66db37fa791b06eb3f9b7c10998af0b98 100644 --- a/substrate/frame/core-fellowship/src/tests/integration.rs +++ b/substrate/frame/core-fellowship/src/tests/integration.rs @@ -27,7 +27,7 @@ use frame_system::EnsureSignedBy; use pallet_ranked_collective::{EnsureRanked, Geometric, Rank, TallyOf, Votes}; use sp_core::Get; use sp_runtime::{ - traits::{Convert, ReduceBy, TryMorphInto}, + traits::{Convert, ReduceBy, ReplaceWithDefault, TryMorphInto}, BuildStorage, DispatchError, }; type Class = Rank; @@ -137,12 +137,14 @@ impl pallet_ranked_collective::Config for Test { // Members can promote up to the rank of 2 below them. MapSuccess, ReduceBy>>, >; + type AddOrigin = MapSuccess>; type DemoteOrigin = EitherOf< // Root can demote arbitrarily. frame_system::EnsureRootWithSuccess>, // Members can demote up to the rank of 3 below them. MapSuccess, ReduceBy>>, >; + type RemoveOrigin = Self::DemoteOrigin; type ExchangeOrigin = EitherOf< // Root can exchange arbitrarily. frame_system::EnsureRootWithSuccess>, diff --git a/substrate/frame/core-fellowship/src/tests/unit.rs b/substrate/frame/core-fellowship/src/tests/unit.rs index 52a31e5e106f2172c1615c3052d4ede5f6bc9749..de8cd858bdfc05d4881ff31331745906e001dc95 100644 --- a/substrate/frame/core-fellowship/src/tests/unit.rs +++ b/substrate/frame/core-fellowship/src/tests/unit.rs @@ -19,6 +19,7 @@ use std::collections::BTreeMap; +use core::cell::RefCell; use frame_support::{ assert_noop, assert_ok, derive_impl, ord_parameter_types, pallet_prelude::Weight, @@ -27,7 +28,6 @@ use frame_support::{ }; use frame_system::EnsureSignedBy; use sp_runtime::{traits::TryMorphInto, BuildStorage, DispatchError, DispatchResult}; -use sp_std::cell::RefCell; use crate as pallet_core_fellowship; use crate::*; diff --git a/substrate/frame/democracy/Cargo.toml b/substrate/frame/democracy/Cargo.toml index 0ade0d58a6d91b44965a85b45efd5532c329ba68..9a55cda5340c2fa795fd79b17ee4e2b5f568bed2 100644 --- a/substrate/frame/democracy/Cargo.toml +++ b/substrate/frame/democracy/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } @@ -28,7 +28,7 @@ sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/substrate/frame/democracy/src/conviction.rs b/substrate/frame/democracy/src/conviction.rs index d2f685f7d99efb79e12628ac38370fb69445a44d..54f4ff524f2a9be397a12eadb4bb0c9e88cbf501 100644 --- a/substrate/frame/democracy/src/conviction.rs +++ b/substrate/frame/democracy/src/conviction.rs @@ -19,12 +19,12 @@ use crate::types::Delegations; use codec::{Decode, Encode, MaxEncodedLen}; +use core::result::Result; use scale_info::TypeInfo; use sp_runtime::{ traits::{Bounded, CheckedDiv, CheckedMul, Zero}, RuntimeDebug, }; -use sp_std::{prelude::*, result::Result}; /// A value denoting the strength of conviction of a vote. #[derive( diff --git a/substrate/frame/democracy/src/migrations/v1.rs b/substrate/frame/democracy/src/migrations/v1.rs index 64baea8f3af7039eb8a2422b48e897aaaa93a664..5e423b9ab6eff7d2a96f94416bdd425827869fba 100644 --- a/substrate/frame/democracy/src/migrations/v1.rs +++ b/substrate/frame/democracy/src/migrations/v1.rs @@ -54,7 +54,7 @@ pub mod v1 { use super::*; /// Migration for translating bare `Hash`es into `Bounded`s. - pub struct Migration(sp_std::marker::PhantomData); + pub struct Migration(core::marker::PhantomData); impl> OnRuntimeUpgrade for Migration { #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/democracy/src/tests.rs b/substrate/frame/democracy/src/tests.rs index 8136fa5c4c97fedd7915ad77fb92e44bd68bb58e..973e0c28eb2f7307d8e18ff3470c7df8d106a609 100644 --- a/substrate/frame/democracy/src/tests.rs +++ b/substrate/frame/democracy/src/tests.rs @@ -29,9 +29,8 @@ use frame_support::{ }; use frame_system::{EnsureRoot, EnsureSigned, EnsureSignedBy}; use pallet_balances::{BalanceLock, Error as BalancesError}; -use sp_core::H256; use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, Hash, IdentityLookup}, + traits::{BadOrigin, BlakeTwo256, Hash}, BuildStorage, Perbill, }; mod cancellation; @@ -81,28 +80,8 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * BlockWeights::get().max_block; diff --git a/substrate/frame/democracy/src/vote_threshold.rs b/substrate/frame/democracy/src/vote_threshold.rs index e8efa179ed8bf1ad68ac34aa2f19d90985143125..82d6ed178f13783f2e9495ce064378b3f8de5ecf 100644 --- a/substrate/frame/democracy/src/vote_threshold.rs +++ b/substrate/frame/democracy/src/vote_threshold.rs @@ -19,11 +19,11 @@ use crate::Tally; use codec::{Decode, Encode, MaxEncodedLen}; +use core::ops::{Add, Div, Mul, Rem}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_runtime::traits::{IntegerSquareRoot, Zero}; -use sp_std::ops::{Add, Div, Mul, Rem}; /// A means of determining if a vote is past pass threshold. #[derive( diff --git a/substrate/frame/election-provider-multi-phase/Cargo.toml b/substrate/frame/election-provider-multi-phase/Cargo.toml index 91bdb3c027ffb45a6ec5f4eca6b1f61ee454cdf6..eadce8c1ff847b2902d2d3ca9eb21d797060da4f 100644 --- a/substrate/frame/election-provider-multi-phase/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = scale-info = { version = "2.10.0", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs index 4a2855f1361f2c7f098a023e74344e1062be7506..957ae51b8f1daf89eb47045c82ca09a6a64bdf93 100644 --- a/substrate/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/substrate/frame/election-provider-multi-phase/src/benchmarking.rs @@ -496,7 +496,7 @@ frame_benchmarking::benchmarks! { let (_, stake, _) = voters[*idx]; stake }).unwrap_or_default(); - sp_std::cmp::Reverse(stake) + core::cmp::Reverse(stake) }); let mut index_assignments = assignments diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml index 05c6a6d404629f8bdc5a9cdb1d2ccdb785972693..e6384450a6fd632f206b7c9bf2bbc870604143e7 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] parking_lot = "0.12.1" codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } scale-info = { version = "2.10.0", features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } sp-runtime = { path = "../../../primitives/runtime" } sp-io = { path = "../../../primitives/io" } @@ -35,7 +35,23 @@ frame-election-provider-support = { path = "../../election-provider-support" } pallet-election-provider-multi-phase = { path = ".." } pallet-staking = { path = "../../staking" } +pallet-nomination-pools = { path = "../../nomination-pools" } pallet-bags-list = { path = "../../bags-list" } pallet-balances = { path = "../../balances" } pallet-timestamp = { path = "../../timestamp" } pallet-session = { path = "../../session" } + +[features] +try-runtime = [ + "frame-election-provider-support/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-bags-list/try-runtime", + "pallet-balances/try-runtime", + "pallet-election-provider-multi-phase/try-runtime", + "pallet-nomination-pools/try-runtime", + "pallet-session/try-runtime", + "pallet-staking/try-runtime", + "pallet-timestamp/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs index 9344fad9d3905e1b3a92ad2ddea58e83ee5e30eb..c5769f8210475f48bfb201d5d6196d15abf3d18f 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs @@ -52,9 +52,9 @@ fn log_current_time() { #[test] fn block_progression_works() { - let (mut ext, pool_state, _) = ExtBuilder::default().build_offchainify(); + let (ext, pool_state, _) = ExtBuilder::default().build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { assert_eq!(active_era(), 0); assert_eq!(Session::current_index(), 0); assert!(ElectionProviderMultiPhase::current_phase().is_off()); @@ -69,9 +69,9 @@ fn block_progression_works() { assert!(ElectionProviderMultiPhase::current_phase().is_signed()); }); - let (mut ext, pool_state, _) = ExtBuilder::default().build_offchainify(); + let (ext, pool_state, _) = ExtBuilder::default().build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { assert_eq!(active_era(), 0); assert_eq!(Session::current_index(), 0); assert!(ElectionProviderMultiPhase::current_phase().is_off()); @@ -92,12 +92,12 @@ fn offchainify_works() { let staking_builder = StakingExtBuilder::default(); let epm_builder = EpmExtBuilder::default(); - let (mut ext, pool_state, _) = ExtBuilder::default() + let (ext, pool_state, _) = ExtBuilder::default() .epm(epm_builder) .staking(staking_builder) .build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { // test ocw progression and solution queue if submission when unsigned phase submission is // not delayed. for _ in 0..100 { @@ -134,7 +134,7 @@ fn offchainify_works() { fn mass_slash_doesnt_enter_emergency_phase() { let epm_builder = EpmExtBuilder::default().disable_emergency_throttling(); let staking_builder = StakingExtBuilder::default().validator_count(7); - let (mut ext, _, _) = ExtBuilder::default() + let (ext, _, _) = ExtBuilder::default() .epm(epm_builder) .staking(staking_builder) .build_offchainify(); @@ -183,12 +183,12 @@ fn continous_slashes_below_offending_threshold() { let staking_builder = StakingExtBuilder::default().validator_count(10); let epm_builder = EpmExtBuilder::default().disable_emergency_throttling(); - let (mut ext, pool_state, _) = ExtBuilder::default() + let (ext, pool_state, _) = ExtBuilder::default() .epm(epm_builder) .staking(staking_builder) .build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { assert_eq!(Session::validators().len(), 10); let mut active_validator_set = Session::validators(); @@ -234,10 +234,10 @@ fn continous_slashes_below_offending_threshold() { fn ledger_consistency_active_balance_below_ed() { use pallet_staking::{Error, Event}; - let (mut ext, pool_state, _) = + let (ext, pool_state, _) = ExtBuilder::default().staking(StakingExtBuilder::default()).build_offchainify(); - ext.execute_with(|| { + execute_with(ext, || { assert_eq!(Staking::ledger(11.into()).unwrap().active, 1000); // unbonding total of active stake fails because the active ledger balance would fall @@ -287,3 +287,141 @@ fn ledger_consistency_active_balance_below_ed() { assert!(Staking::ledger(11.into()).is_err()); }); } + +#[test] +/// Automatic withdrawal of unlocking funds in staking propagates to the nomination pools and its +/// state correctly. +/// +/// The staking pallet may withdraw unlocking funds from a pool's bonded account without a pool +/// member or operator calling explicitly `Call::withdraw*`. This test verifies that the member's +/// are eventually paid and the `TotalValueLocked` is kept in sync in those cases. +fn automatic_unbonding_pools() { + use pallet_nomination_pools::TotalValueLocked; + + // closure to fetch the staking unlocking chunks of an account. + let unlocking_chunks_of = |account: AccountId| -> usize { + Staking::ledger(sp_staking::StakingAccount::Controller(account)) + .unwrap() + .unlocking + .len() + }; + + let (ext, pool_state, _) = ExtBuilder::default() + .pools(PoolsExtBuilder::default().max_unbonding(1)) + .staking(StakingExtBuilder::default().max_unlocking(1).bonding_duration(2)) + .build_offchainify(); + + execute_with(ext, || { + assert_eq!(::MaxUnlockingChunks::get(), 1); + assert_eq!(::BondingDuration::get(), 2); + assert_eq!(::MaxUnbonding::get(), 1); + + // init state of pool members. + let init_free_balance_2 = Balances::free_balance(2); + let init_free_balance_3 = Balances::free_balance(3); + + let pool_bonded_account = Pools::create_bonded_account(1); + + // creates a pool with 5 bonded, owned by 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(1), 5, 1, 1, 1)); + assert_eq!(locked_amount_for(pool_bonded_account), 5); + + let init_tvl = TotalValueLocked::::get(); + + // 2 joins the pool. + assert_ok!(Pools::join(RuntimeOrigin::signed(2), 10, 1)); + assert_eq!(locked_amount_for(pool_bonded_account), 15); + + // 3 joins the pool. + assert_ok!(Pools::join(RuntimeOrigin::signed(3), 10, 1)); + assert_eq!(locked_amount_for(pool_bonded_account), 25); + + assert_eq!(TotalValueLocked::::get(), 25); + + // currently unlocking 0 chunks in the bonded pools ledger. + assert_eq!(unlocking_chunks_of(pool_bonded_account), 0); + + // unbond 2 from pool. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(2), 2, 10)); + + // amount is still locked in the pool, needs to wait for unbonding period. + assert_eq!(locked_amount_for(pool_bonded_account), 25); + + // max chunks in the ledger are now filled up (`MaxUnlockingChunks == 1`). + assert_eq!(unlocking_chunks_of(pool_bonded_account), 1); + + // tries to unbond 3 from pool. it will fail since there are no unlocking chunks left + // available and the current in the queue haven't been there for more than bonding + // duration. + assert_err!( + Pools::unbond(RuntimeOrigin::signed(3), 3, 10), + pallet_staking::Error::::NoMoreChunks + ); + + assert_eq!(current_era(), 0); + + // progress over bonding duration. + for _ in 0..=::BondingDuration::get() { + start_next_active_era(pool_state.clone()).unwrap(); + } + assert_eq!(current_era(), 3); + System::reset_events(); + + let locked_before_withdraw_pool = locked_amount_for(pool_bonded_account); + assert_eq!(Balances::free_balance(pool_bonded_account), 26); + + // now unbonding 3 will work, although the pool's ledger still has the unlocking chunks + // filled up. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(3), 3, 10)); + assert_eq!(unlocking_chunks_of(pool_bonded_account), 1); + + assert_eq!( + staking_events(), + [ + // auto-withdraw happened as expected to release 2's unbonding funds, but the funds + // were not transfered to 2 and stay in the pool's tranferrable balance instead. + pallet_staking::Event::Withdrawn { stash: 7939698191839293293, amount: 10 }, + pallet_staking::Event::Unbonded { stash: 7939698191839293293, amount: 10 } + ] + ); + + // balance of the pool remains the same, it hasn't withdraw explicitly from the pool yet. + assert_eq!(Balances::free_balance(pool_bonded_account), 26); + // but the locked amount in the pool's account decreases due to the auto-withdraw: + assert_eq!(locked_before_withdraw_pool - 10, locked_amount_for(pool_bonded_account)); + + // TVL correctly updated. + assert_eq!(TotalValueLocked::::get(), 25 - 10); + + // however, note that the withdrawing from the pool still works for 2, the funds are taken + // from the pool's free balance. + assert_eq!(Balances::free_balance(pool_bonded_account), 26); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(2), 2, 10)); + assert_eq!(Balances::free_balance(pool_bonded_account), 16); + + assert_eq!(Balances::free_balance(2), 20); + assert_eq!(TotalValueLocked::::get(), 15); + + // 3 cannot withdraw yet. + assert_err!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10), + pallet_nomination_pools::Error::::CannotWithdrawAny + ); + + // progress over bonding duration. + for _ in 0..=::BondingDuration::get() { + start_next_active_era(pool_state.clone()).unwrap(); + } + assert_eq!(current_era(), 6); + System::reset_events(); + + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10)); + + // final conditions are the expected. + assert_eq!(Balances::free_balance(pool_bonded_account), 6); // 5 init bonded + ED + assert_eq!(Balances::free_balance(2), init_free_balance_2); + assert_eq!(Balances::free_balance(3), init_free_balance_3); + + assert_eq!(TotalValueLocked::::get(), init_tvl); + }); +} diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index bfa42a776ce2651b37df37300760799e6eb3daa4..07c15999c247580cb113ef8b2ebacd69d1608d65 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -69,6 +69,7 @@ frame_support::construct_runtime!( System: frame_system, ElectionProviderMultiPhase: pallet_election_provider_multi_phase, Staking: pallet_staking, + Pools: pallet_nomination_pools, Balances: pallet_balances, BagsList: pallet_bags_list, Session: pallet_session, @@ -114,7 +115,7 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = traits::ConstU32<1>; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); + type FreezeIdentifier = RuntimeFreezeReason; type WeightInfo = (); } @@ -233,7 +234,7 @@ const THRESHOLDS: [VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_00 parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; pub const SessionsPerEra: sp_staking::SessionIndex = 2; - pub const BondingDuration: sp_staking::EraIndex = 28; + pub static BondingDuration: sp_staking::EraIndex = 28; pub const SlashDeferDuration: sp_staking::EraIndex = 7; // 1/4 the bonding duration. pub HistoryDepth: u32 = 84; } @@ -246,6 +247,45 @@ impl pallet_bags_list::Config for Runtime { type Score = VoteWeight; } +pub struct BalanceToU256; +impl sp_runtime::traits::Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl sp_runtime::traits::Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub const PoolsPalletId: frame_support::PalletId = frame_support::PalletId(*b"py/nopls"); + pub static MaxUnbonding: u32 = 8; +} + +impl pallet_nomination_pools::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type RuntimeFreezeReason = RuntimeFreezeReason; + type RewardCounter = sp_runtime::FixedU128; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type Staking = Staking; + type PostUnbondingPoolsWindow = ConstU32<2>; + type PalletId = PoolsPalletId; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = MaxUnbonding; + type MaxPointsToBalance = frame_support::traits::ConstU8<10>; +} + +parameter_types! { + pub static MaxUnlockingChunks: u32 = 32; +} + /// Upper limit on the number of NPOS nominations. const MAX_QUOTA_NOMINATIONS: u32 = 16; /// Disabling factor set explicitly to byzantine threshold @@ -273,10 +313,10 @@ impl pallet_staking::Config for Runtime { type VoterList = BagsList; type NominationsQuota = pallet_staking::FixedNominationsQuota; type TargetList = pallet_staking::UseValidatorsMap; - type MaxUnlockingChunks = ConstU32<32>; + type MaxUnlockingChunks = MaxUnlockingChunks; type MaxControllersInDeprecationBatch = ConstU32<100>; type HistoryDepth = HistoryDepth; - type EventListeners = (); + type EventListeners = Pools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type DisablingStrategy = @@ -396,6 +436,14 @@ impl StakingExtBuilder { self.validator_count = n; self } + pub fn max_unlocking(self, max: u32) -> Self { + ::set(max); + self + } + pub fn bonding_duration(self, eras: EraIndex) -> Self { + ::set(eras); + self + } } pub struct EpmExtBuilder {} @@ -419,6 +467,21 @@ impl EpmExtBuilder { } } +pub struct PoolsExtBuilder {} + +impl Default for PoolsExtBuilder { + fn default() -> Self { + PoolsExtBuilder {} + } +} + +impl PoolsExtBuilder { + pub fn max_unbonding(self, max: u32) -> Self { + ::set(max); + self + } +} + pub struct BalancesExtBuilder { balances: Vec<(AccountId, Balance)>, } @@ -466,6 +529,7 @@ pub struct ExtBuilder { staking_builder: StakingExtBuilder, epm_builder: EpmExtBuilder, balances_builder: BalancesExtBuilder, + pools_builder: PoolsExtBuilder, } impl Default for ExtBuilder { @@ -474,6 +538,7 @@ impl Default for ExtBuilder { staking_builder: StakingExtBuilder::default(), epm_builder: EpmExtBuilder::default(), balances_builder: BalancesExtBuilder::default(), + pools_builder: PoolsExtBuilder::default(), } } } @@ -550,6 +615,11 @@ impl ExtBuilder { self } + pub fn pools(mut self, builder: PoolsExtBuilder) -> Self { + self.pools_builder = builder; + self + } + pub fn balances(mut self, builder: BalancesExtBuilder) -> Self { self.balances_builder = builder; self @@ -569,20 +639,20 @@ impl ExtBuilder { (ext, pool_state, offchain_state) } +} - pub fn build_and_execute(self, test: impl FnOnce() -> ()) { - let mut ext = self.build(); - ext.execute_with(test); +pub(crate) fn execute_with(mut ext: sp_io::TestExternalities, test: impl FnOnce() -> ()) { + ext.execute_with(test); - #[cfg(feature = "try-runtime")] - ext.execute_with(|| { - let bn = System::block_number(); + #[cfg(feature = "try-runtime")] + ext.execute_with(|| { + let bn = System::block_number(); - assert_ok!(>::try_state(bn)); - assert_ok!(>::try_state(bn)); - assert_ok!(>::try_state(bn)); - }); - } + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + }); } // Progress to given block, triggering session and era changes as we progress and ensuring that @@ -608,6 +678,7 @@ pub fn roll_to(n: BlockNumber, delay_solution: bool) { if b != n { Staking::on_finalize(System::block_number()); } + Pools::on_initialize(b); log_current_time(); } @@ -858,6 +929,11 @@ pub(crate) fn set_minimum_election_score( .map_err(|_| ()) } +pub(crate) fn locked_amount_for(account_id: AccountId) -> Balance { + let lock = pallet_balances::Locks::::get(account_id); + lock[0].amount +} + pub(crate) fn staking_events() -> Vec> { System::events() .into_iter() diff --git a/substrate/frame/election-provider-support/solution-type/Cargo.toml b/substrate/frame/election-provider-support/solution-type/Cargo.toml index 0b410364690305ecd90ad90ce7cbda57875d1982..1bf1165229a7dd20700bb561d4dd776ca7c250bb 100644 --- a/substrate/frame/election-provider-support/solution-type/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/Cargo.toml @@ -18,8 +18,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "2.0.48", features = ["full", "visit"] } -quote = "1.0.28" +syn = { features = ["full", "visit"], workspace = true } +quote = { workspace = true } proc-macro2 = "1.0.56" proc-macro-crate = "3.0.0" diff --git a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index 7200d207aeccf9c3b83202bce7ad61bfc4c289d5..8a73dd38fa2df251fcd0ef5826f51b6cf56e0160 100644 --- a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["small_rng", "std"] } diff --git a/substrate/frame/election-provider-support/src/tests.rs b/substrate/frame/election-provider-support/src/tests.rs index 73ce1427cf2f00883865a4b8c6cc5fad864d9c13..6e3deb9e38346629bbff92dfc708e9bbf5ad5a9d 100644 --- a/substrate/frame/election-provider-support/src/tests.rs +++ b/substrate/frame/election-provider-support/src/tests.rs @@ -29,7 +29,7 @@ mod solution_type { // these need to come from the same dev-dependency `frame-election-provider-support`, not from // the crate. use crate::{generate_solution_type, Assignment, Error as NposError, NposSolution}; - use sp_std::fmt::Debug; + use core::fmt::Debug; #[allow(dead_code)] mod __private { diff --git a/substrate/frame/election-provider-support/src/weights.rs b/substrate/frame/election-provider-support/src/weights.rs index addb6ad8d03069aecd6fda3b2725cdbc37e5ba5d..09b0cb108f7faa39bce20aaf9e4ae3dd9edcf4a5 100644 --- a/substrate/frame/election-provider-support/src/weights.rs +++ b/substrate/frame/election-provider-support/src/weights.rs @@ -41,7 +41,7 @@ #![allow(unused_imports)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_election_provider_support_benchmarking. pub trait WeightInfo { diff --git a/substrate/frame/elections-phragmen/Cargo.toml b/substrate/frame/elections-phragmen/Cargo.toml index 4f8c5638d4bf4825295bbd1ffe6909aebf4f9d55..4dc4a3454aa031da708f536aadcbe9764b093726 100644 --- a/substrate/frame/elections-phragmen/Cargo.toml +++ b/substrate/frame/elections-phragmen/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.14", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index 5d20a3cfcee2e72ace6b70adaee85e0ed1cdd7bf..a078361a5f713b5d267fdc1002ac361f95a3faef 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -1313,39 +1313,13 @@ mod tests { traits::{ConstU32, ConstU64, OnInitialize}, }; use frame_system::ensure_signed; - use sp_core::H256; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, - }; + use sp_runtime::{testing::Header, BuildStorage}; use substrate_test_utils::assert_eq_uvec; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/examples/basic/Cargo.toml b/substrate/frame/examples/basic/Cargo.toml index 2be5aecb9681d990366f8d79bf6b6020069ffb99..e4ab5112201d1661d1bbc991489bb6d847d6c32d 100644 --- a/substrate/frame/examples/basic/Cargo.toml +++ b/substrate/frame/examples/basic/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../../benchmarking", default-features = false, optional = true } frame-support = { path = "../../support", default-features = false } diff --git a/substrate/frame/examples/basic/src/benchmarking.rs b/substrate/frame/examples/basic/src/benchmarking.rs index 4b2ebb41fbda1d6d8a0d12a65b32391510f1a488..65ca3089aba4247cae65932cd4bca9b68d520b75 100644 --- a/substrate/frame/examples/basic/src/benchmarking.rs +++ b/substrate/frame/examples/basic/src/benchmarking.rs @@ -48,7 +48,7 @@ mod benchmarks { set_dummy(RawOrigin::Root, value); // The execution phase is just running `set_dummy` extrinsic call // This is the optional benchmark verification phase, asserting certain states. - assert_eq!(Pallet::::dummy(), Some(value)) + assert_eq!(Dummy::::get(), Some(value)) } // An example method that returns a Result that can be called within a benchmark diff --git a/substrate/frame/examples/basic/src/lib.rs b/substrate/frame/examples/basic/src/lib.rs index 5eff74922cabfa8e10b747ba186b7974e81c49f4..12cadc969fd74288fc2ca99ac014fa98a603d56d 100644 --- a/substrate/frame/examples/basic/src/lib.rs +++ b/substrate/frame/examples/basic/src/lib.rs @@ -54,6 +54,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; +use core::marker::PhantomData; use frame_support::{ dispatch::{ClassifyDispatch, DispatchClass, DispatchResult, Pays, PaysFee, WeighData}, traits::IsSubType, @@ -68,7 +69,7 @@ use sp_runtime::{ InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, }, }; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_std::vec::Vec; // Re-export pallet items so that they can be accessed from the crate namespace. pub use pallet::*; @@ -285,9 +286,7 @@ pub mod pallet { let _sender = ensure_signed(origin)?; // Read the value of dummy from storage. - // let dummy = Self::dummy(); - // Will also work using the `::get` on the storage item type itself: - // let dummy = >::get(); + // let dummy = Dummy::::get(); // Calculate the new value. // let new_dummy = dummy.map_or(increase_by, |dummy| dummy + increase_by); @@ -380,20 +379,14 @@ pub mod pallet { // - `Foo::put(1); Foo::get()` returns `1`; // - `Foo::kill(); Foo::get()` returns `0` (u32::default()). #[pallet::storage] - // The getter attribute generate a function on `Pallet` placeholder: - // `fn getter_name() -> Type` for basic value items or - // `fn getter_name(key: KeyType) -> ValueType` for map items. - #[pallet::getter(fn dummy)] pub(super) type Dummy = StorageValue<_, T::Balance>; // A map that has enumerable entries. #[pallet::storage] - #[pallet::getter(fn bar)] pub(super) type Bar = StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance>; // this one uses the query kind: `ValueQuery`, we'll demonstrate the usage of 'mutate' API. #[pallet::storage] - #[pallet::getter(fn foo)] pub(super) type Foo = StorageValue<_, T::Balance, ValueQuery>; #[pallet::storage] @@ -432,10 +425,10 @@ impl Pallet { fn accumulate_foo(origin: T::RuntimeOrigin, increase_by: T::Balance) -> DispatchResult { let _sender = ensure_signed(origin)?; - let prev = >::get(); + let prev = Foo::::get(); // Because Foo has 'default', the type of 'foo' in closure is the raw type instead of an // Option<> type. - let result = >::mutate(|foo| { + let result = Foo::::mutate(|foo| { *foo = foo.saturating_add(increase_by); *foo }); @@ -485,8 +478,8 @@ impl Pallet { #[scale_info(skip_type_params(T))] pub struct WatchDummy(PhantomData); -impl sp_std::fmt::Debug for WatchDummy { - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { +impl core::fmt::Debug for WatchDummy { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "WatchDummy") } } @@ -501,7 +494,7 @@ where type AdditionalSigned = (); type Pre = (); - fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + fn additional_signed(&self) -> core::result::Result<(), TransactionValidityError> { Ok(()) } diff --git a/substrate/frame/examples/basic/src/tests.rs b/substrate/frame/examples/basic/src/tests.rs index 9434ace35ffe35467a9c513d67f0450e5b6ade6e..207e46e428ddc62359621bd11eb226e54ab3f92e 100644 --- a/substrate/frame/examples/basic/src/tests.rs +++ b/substrate/frame/examples/basic/src/tests.rs @@ -119,25 +119,25 @@ fn it_works_for_optional_value() { // Check that GenesisBuilder works properly. let val1 = 42; let val2 = 27; - assert_eq!(Example::dummy(), Some(val1)); + assert_eq!(Dummy::::get(), Some(val1)); // Check that accumulate works when we have Some value in Dummy already. assert_ok!(Example::accumulate_dummy(RuntimeOrigin::signed(1), val2)); - assert_eq!(Example::dummy(), Some(val1 + val2)); + assert_eq!(Dummy::::get(), Some(val1 + val2)); // Check that accumulate works when we Dummy has None in it. >::on_initialize(2); assert_ok!(Example::accumulate_dummy(RuntimeOrigin::signed(1), val1)); - assert_eq!(Example::dummy(), Some(val1 + val2 + val1)); + assert_eq!(Dummy::::get(), Some(val1 + val2 + val1)); }); } #[test] fn it_works_for_default_value() { new_test_ext().execute_with(|| { - assert_eq!(Example::foo(), 24); + assert_eq!(Foo::::get(), 24); assert_ok!(Example::accumulate_foo(RuntimeOrigin::signed(1), 1)); - assert_eq!(Example::foo(), 25); + assert_eq!(Foo::::get(), 25); }); } @@ -146,7 +146,7 @@ fn set_dummy_works() { new_test_ext().execute_with(|| { let test_val = 133; assert_ok!(Example::set_dummy(RuntimeOrigin::root(), test_val.into())); - assert_eq!(Example::dummy(), Some(test_val)); + assert_eq!(Dummy::::get(), Some(test_val)); }); } diff --git a/substrate/frame/examples/basic/src/weights.rs b/substrate/frame/examples/basic/src/weights.rs index def944054cce8a8b5230781385fd2427e8949603..dbb9170aa67e0565b91b9bf920435f27c188773d 100644 --- a/substrate/frame/examples/basic/src/weights.rs +++ b/substrate/frame/examples/basic/src/weights.rs @@ -42,7 +42,7 @@ #![allow(unused_imports)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_example_basic. pub trait WeightInfo { diff --git a/substrate/frame/examples/default-config/Cargo.toml b/substrate/frame/examples/default-config/Cargo.toml index 83d9ae79510da26280a2f150e70312d35eeb07fb..e40845a425a29d8d2868e823c1ba34dbabd136c0 100644 --- a/substrate/frame/examples/default-config/Cargo.toml +++ b/substrate/frame/examples/default-config/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } frame-system = { path = "../../system", default-features = false } diff --git a/substrate/frame/examples/dev-mode/Cargo.toml b/substrate/frame/examples/dev-mode/Cargo.toml index f150f446446cf99da82f4a5535a8e8f031d54b73..a9c4e3f3b1fccb9085f1bc9a6a2f018a055d683a 100644 --- a/substrate/frame/examples/dev-mode/Cargo.toml +++ b/substrate/frame/examples/dev-mode/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } frame-system = { path = "../../system", default-features = false } diff --git a/substrate/frame/examples/kitchensink/Cargo.toml b/substrate/frame/examples/kitchensink/Cargo.toml index 4255ebb66b650efb77264b250396c0a05ba0763a..37384107530ee4dca645f12f1b50e4af5547dabc 100644 --- a/substrate/frame/examples/kitchensink/Cargo.toml +++ b/substrate/frame/examples/kitchensink/Cargo.toml @@ -17,10 +17,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -frame-support = { path = "../../support", default-features = false } +frame-support = { path = "../../support", default-features = false, features = ["experimental"] } frame-system = { path = "../../system", default-features = false } sp-io = { path = "../../../primitives/io", default-features = false } diff --git a/substrate/frame/examples/kitchensink/src/benchmarking.rs b/substrate/frame/examples/kitchensink/src/benchmarking.rs index 24da581fc967b8e8c8586bf3b41a4452a89a4dcd..5f1d378e06fe67ee3978124ce7b557ad39b89e0a 100644 --- a/substrate/frame/examples/kitchensink/src/benchmarking.rs +++ b/substrate/frame/examples/kitchensink/src/benchmarking.rs @@ -51,7 +51,7 @@ mod benchmarks { set_foo(RawOrigin::Root, value, 10u128); // The execution phase is just running `set_foo` extrinsic call // This is the optional benchmark verification phase, asserting certain states. - assert_eq!(Pallet::::foo(), Some(value)) + assert_eq!(Foo::::get(), Some(value)) } // This line generates test cases for benchmarking, and could be run by: diff --git a/substrate/frame/examples/kitchensink/src/lib.rs b/substrate/frame/examples/kitchensink/src/lib.rs index 18429bc967d7c1e1f7b181e708ca3a86ef6f251c..b7425b0c0846afc3e9488a490144a89e7b647301 100644 --- a/substrate/frame/examples/kitchensink/src/lib.rs +++ b/substrate/frame/examples/kitchensink/src/lib.rs @@ -125,7 +125,6 @@ pub mod pallet { #[pallet::storage] #[pallet::unbounded] // optional #[pallet::storage_prefix = "OtherFoo"] // optional - #[pallet::getter(fn foo)] // optional pub type Foo = StorageValue; #[pallet::type_value] diff --git a/substrate/frame/examples/offchain-worker/Cargo.toml b/substrate/frame/examples/offchain-worker/Cargo.toml index cc337707a2962120eb035d355c328705381a152d..fc5151ff292b4571d9cb898d6246668d5f3b4dc8 100644 --- a/substrate/frame/examples/offchain-worker/Cargo.toml +++ b/substrate/frame/examples/offchain-worker/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } lite-json = { version = "0.2.0", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } frame-system = { path = "../../system", default-features = false } diff --git a/substrate/frame/examples/offchain-worker/src/lib.rs b/substrate/frame/examples/offchain-worker/src/lib.rs index 6c1fa6ea8ec42dbee21875a0610b3724aadff802..d21c8b4cfd24971f945b73c558463ee7a1f47f77 100644 --- a/substrate/frame/examples/offchain-worker/src/lib.rs +++ b/substrate/frame/examples/offchain-worker/src/lib.rs @@ -332,7 +332,6 @@ pub mod pallet { /// /// This is used to calculate average price, should have bounded size. #[pallet::storage] - #[pallet::getter(fn prices)] pub(super) type Prices = StorageValue<_, BoundedVec, ValueQuery>; /// Defines the block when next unsigned transaction will be accepted. @@ -341,7 +340,6 @@ pub mod pallet { /// we only allow one transaction every `T::UnsignedInterval` blocks. /// This storage entry defines when new transaction is going to be accepted. #[pallet::storage] - #[pallet::getter(fn next_unsigned_at)] pub(super) type NextUnsignedAt = StorageValue<_, BlockNumberFor, ValueQuery>; } @@ -479,7 +477,7 @@ impl Pallet { ) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. - let next_unsigned_at = >::get(); + let next_unsigned_at = NextUnsignedAt::::get(); if next_unsigned_at > block_number { return Err("Too early to send unsigned transaction") } @@ -513,7 +511,7 @@ impl Pallet { ) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. - let next_unsigned_at = >::get(); + let next_unsigned_at = NextUnsignedAt::::get(); if next_unsigned_at > block_number { return Err("Too early to send unsigned transaction") } @@ -543,7 +541,7 @@ impl Pallet { ) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. - let next_unsigned_at = >::get(); + let next_unsigned_at = NextUnsignedAt::::get(); if next_unsigned_at > block_number { return Err("Too early to send unsigned transaction") } @@ -664,7 +662,7 @@ impl Pallet { /// Calculate current average price. fn average_price() -> Option { - let prices = >::get(); + let prices = Prices::::get(); if prices.is_empty() { None } else { @@ -677,7 +675,7 @@ impl Pallet { new_price: &u32, ) -> TransactionValidity { // Now let's check if the transaction has any chance to succeed. - let next_unsigned_at = >::get(); + let next_unsigned_at = NextUnsignedAt::::get(); if &next_unsigned_at > block_number { return InvalidTransaction::Stale.into() } diff --git a/substrate/frame/examples/split/Cargo.toml b/substrate/frame/examples/split/Cargo.toml index 733ca92b820d6b8a4230f7c8868494ec0410656e..d140fc3eef43b04ca7b9175efbdae2830e2d6282 100644 --- a/substrate/frame/examples/split/Cargo.toml +++ b/substrate/frame/examples/split/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } diff --git a/substrate/frame/examples/split/src/lib.rs b/substrate/frame/examples/split/src/lib.rs index 74d2e0cc24b7bf5789da19f2657c5e7d2514bbfe..5245d90e390cff1ccab79ecc279fe57188cde030 100644 --- a/substrate/frame/examples/split/src/lib.rs +++ b/substrate/frame/examples/split/src/lib.rs @@ -107,7 +107,7 @@ pub mod pallet { let _who = ensure_signed(origin)?; // Read a value from storage. - match >::get() { + match Something::::get() { // Return an error if the value has not been set. None => return Err(Error::::NoneValue.into()), Some(old) => { diff --git a/substrate/frame/examples/tasks/Cargo.toml b/substrate/frame/examples/tasks/Cargo.toml index f6850b53c030262d49b59205c1dec22923f1a38d..41521114366a298861c883b4d6f14b0eaf6c4276 100644 --- a/substrate/frame/examples/tasks/Cargo.toml +++ b/substrate/frame/examples/tasks/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../../support", default-features = false } diff --git a/substrate/frame/executive/Cargo.toml b/substrate/frame/executive/Cargo.toml index 7c72fc77be90a12c055329b3e71c07bfb0167bc1..a4ca265f6178218791bf83f6b8f259821e086a98 100644 --- a/substrate/frame/executive/Cargo.toml +++ b/substrate/frame/executive/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/fast-unstake/Cargo.toml b/substrate/frame/fast-unstake/Cargo.toml index 5d0a5410f8db0e32daf44b9523cd8b85614325ab..eca8247845e2a5990ee98426866fdf5ac929034d 100644 --- a/substrate/frame/fast-unstake/Cargo.toml +++ b/substrate/frame/fast-unstake/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/glutton/Cargo.toml b/substrate/frame/glutton/Cargo.toml index b9543e7f47c041202767ae35be932721417f52ce..7de18080b879edb369811a321501c8fe0630e3b7 100644 --- a/substrate/frame/glutton/Cargo.toml +++ b/substrate/frame/glutton/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] blake2 = { version = "0.10.4", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.14", default-features = false } +log = { workspace = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/glutton/src/mock.rs b/substrate/frame/glutton/src/mock.rs index 26863811e29a76b1f8b3249050287e90c39a713b..0049800d95298386a9ed63e23c608d4a2938c758 100644 --- a/substrate/frame/glutton/src/mock.rs +++ b/substrate/frame/glutton/src/mock.rs @@ -18,15 +18,8 @@ use super::*; use crate as pallet_glutton; -use frame_support::{ - assert_ok, derive_impl, - traits::{ConstU32, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use frame_support::{assert_ok, derive_impl}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -40,29 +33,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl Config for Test { diff --git a/substrate/frame/grandpa/Cargo.toml b/substrate/frame/grandpa/Cargo.toml index 3775ccdac1dc2cddaa59f995d51eae76b4495028..db540564fbe7bda05b006b5fb7d2b6da2e33d7c5 100644 --- a/substrate/frame/grandpa/Cargo.toml +++ b/substrate/frame/grandpa/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/grandpa/src/migrations.rs b/substrate/frame/grandpa/src/migrations.rs index 3a484eb60d284c3988a1c2e1fad49bdc10d03b6a..9d0522c3a6e7281d206d4d7e606a22362a801f49 100644 --- a/substrate/frame/grandpa/src/migrations.rs +++ b/substrate/frame/grandpa/src/migrations.rs @@ -35,7 +35,7 @@ mod v5; /// This migration should be added with a runtime upgrade that introduces the /// `MaxSetIdSessionEntries` constant to the pallet (although it could also be /// done later on). -pub struct CleanupSetIdSessionMap(sp_std::marker::PhantomData); +pub struct CleanupSetIdSessionMap(core::marker::PhantomData); impl OnRuntimeUpgrade for CleanupSetIdSessionMap { fn on_runtime_upgrade() -> Weight { // NOTE: since this migration will loop over all stale entries in the diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index ab457033a70cd7e304eb69fa2c99964e5e773bdd..96e5eaf1c8204ec945040a5879c441ecc3f442e9 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -38,7 +38,7 @@ use sp_runtime::{ curve::PiecewiseLinear, impl_opaque_keys, testing::{TestXt, UintAuthorityId}, - traits::{IdentityLookup, OpaqueKeys}, + traits::OpaqueKeys, BuildStorage, DigestItem, Perbill, }; use sp_staking::{EraIndex, SessionIndex}; @@ -68,29 +68,8 @@ impl_opaque_keys! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl frame_system::offchain::SendTransactionTypes for Test diff --git a/substrate/frame/identity/Cargo.toml b/substrate/frame/identity/Cargo.toml index ba1fd500f707566743ea4aa3c3b80ddd4a9d167b..912444bf603606e885d7b0c38c8f8bccbbe93d34 100644 --- a/substrate/frame/identity/Cargo.toml +++ b/substrate/frame/identity/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } enumflags2 = { version = "0.7.7" } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/identity/src/tests.rs b/substrate/frame/identity/src/tests.rs index 5c9304ca8c13d9eac1ed1f6e8a90d8ba0b5493f1..60866f12baa61783aa27333f66a07193059ce727 100644 --- a/substrate/frame/identity/src/tests.rs +++ b/substrate/frame/identity/src/tests.rs @@ -55,29 +55,10 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/im-online/Cargo.toml b/substrate/frame/im-online/Cargo.toml index 04c35908c535513f1c0aa0b5741f36281a01646b..038cbbcd678cca4d947eb4750e11314f643f2ad9 100644 --- a/substrate/frame/im-online/Cargo.toml +++ b/substrate/frame/im-online/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/im-online/src/migration.rs b/substrate/frame/im-online/src/migration.rs index 84652965972e3aecbdd8edba67cb3f5f1b33e49c..0d2c0a055b6d88855bc8783b65e06f0427197208 100644 --- a/substrate/frame/im-online/src/migration.rs +++ b/substrate/frame/im-online/src/migration.rs @@ -56,7 +56,7 @@ pub mod v1 { use super::*; /// Simple migration that replaces `ReceivedHeartbeats` values with `true`. - pub struct Migration(sp_std::marker::PhantomData); + pub struct Migration(core::marker::PhantomData); impl OnRuntimeUpgrade for Migration { #[cfg(feature = "try-runtime")] @@ -116,6 +116,21 @@ pub mod v1 { } } +/// Clears the pallet's offchain storage. +/// +/// Must be put in `OffchainWorkerApi::offchain_worker` after +/// the pallet was removed. +pub fn clear_offchain_storage(validator_set_size: u32) { + (0..validator_set_size).for_each(|idx| { + let key = { + let mut key = DB_PREFIX.to_vec(); + key.extend(idx.encode()); + key + }; + sp_runtime::offchain::storage::StorageValueRef::persistent(&key).clear(); + }); +} + #[cfg(all(feature = "try-runtime", test))] mod test { use super::*; diff --git a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs index 00f1055f6f271c9ef21b914a499c652161f551f1..04f8cda6541dd37685b86b7a25f7b84d5880de45 100644 --- a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs +++ b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs @@ -163,14 +163,11 @@ mod tests { use crate as pallet_insecure_randomness_collective_flip; use sp_core::H256; - use sp_runtime::{ - traits::{BlakeTwo256, Header as _, IdentityLookup}, - BuildStorage, - }; + use sp_runtime::{traits::Header as _, BuildStorage}; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, OnInitialize, Randomness}, + traits::{OnInitialize, Randomness}, }; use frame_system::limits; @@ -191,29 +188,7 @@ mod tests { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = BlockLength; - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_insecure_randomness_collective_flip::Config for Test {} diff --git a/substrate/frame/lottery/src/mock.rs b/substrate/frame/lottery/src/mock.rs index 0b73bba61355a283785ec95ecfa744174f279af0..563ce7202ec39c5b16af1efc58c0cde077400db7 100644 --- a/substrate/frame/lottery/src/mock.rs +++ b/substrate/frame/lottery/src/mock.rs @@ -26,11 +26,7 @@ use frame_support::{ }; use frame_support_test::TestRandomness; use frame_system::EnsureRoot; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, Perbill, -}; +use sp_runtime::{BuildStorage, Perbill}; type Block = frame_system::mocking::MockBlock; @@ -49,29 +45,8 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/membership/Cargo.toml b/substrate/frame/membership/Cargo.toml index 658bf67f12f2c67d9abe84984437ce98c4373958..64214670292739395dc38a024085dec52868d275 100644 --- a/substrate/frame/membership/Cargo.toml +++ b/substrate/frame/membership/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/membership/src/lib.rs b/substrate/frame/membership/src/lib.rs index 0e03c097d1233e5b253fbf3be86faf44b065f145..f32263970038d967a5e8ea697d4a9ff3ef9d26ca 100644 --- a/substrate/frame/membership/src/lib.rs +++ b/substrate/frame/membership/src/lib.rs @@ -27,7 +27,7 @@ use frame_support::{ traits::{ChangeMembers, Contains, Get, InitializeMembers, SortedMembers}, BoundedVec, }; -use sp_runtime::traits::StaticLookup; +use sp_runtime::traits::{StaticLookup, UniqueSaturatedInto}; use sp_std::prelude::*; pub mod migrations; @@ -163,12 +163,16 @@ pub mod pallet { /// /// 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 { + #[pallet::weight(T::WeightInfo::add_member(T::MaxMembers::get()))] + pub fn add_member( + origin: OriginFor, + who: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { T::AddOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; let mut members = >::get(); + let init_length = members.len(); let location = members.binary_search(&who).err().ok_or(Error::::AlreadyMember)?; members .try_insert(location, who.clone()) @@ -179,19 +183,24 @@ pub mod pallet { T::MembershipChanged::change_members_sorted(&[who], &[], &members[..]); Self::deposit_event(Event::MemberAdded); - Ok(()) + + Ok(Some(T::WeightInfo::add_member(init_length as u32)).into()) } /// 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 { + #[pallet::weight(T::WeightInfo::remove_member(T::MaxMembers::get()))] + pub fn remove_member( + origin: OriginFor, + who: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { T::RemoveOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; let mut members = >::get(); + let init_length = members.len(); let location = members.binary_search(&who).ok().ok_or(Error::::NotMember)?; members.remove(location); @@ -201,7 +210,7 @@ pub mod pallet { Self::rejig_prime(&members); Self::deposit_event(Event::MemberRemoved); - Ok(()) + Ok(Some(T::WeightInfo::remove_member(init_length as u32)).into()) } /// Swap out one member `remove` for another `add`. @@ -210,18 +219,18 @@ pub mod pallet { /// /// Prime membership is *not* passed from `remove` to `add`, if extant. #[pallet::call_index(2)] - #[pallet::weight({50_000_000})] + #[pallet::weight(T::WeightInfo::swap_member(T::MaxMembers::get()))] pub fn swap_member( origin: OriginFor, remove: AccountIdLookupOf, add: AccountIdLookupOf, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { T::SwapOrigin::ensure_origin(origin)?; let remove = T::Lookup::lookup(remove)?; let add = T::Lookup::lookup(add)?; if remove == add { - return Ok(()) + return Ok(().into()); } let mut members = >::get(); @@ -236,7 +245,7 @@ pub mod pallet { Self::rejig_prime(&members); Self::deposit_event(Event::MembersSwapped); - Ok(()) + Ok(Some(T::WeightInfo::swap_member(members.len() as u32)).into()) } /// Change the membership to a new set, disregarding the existing membership. Be nice and @@ -244,7 +253,7 @@ pub mod pallet { /// /// May only be called from `T::ResetOrigin`. #[pallet::call_index(3)] - #[pallet::weight({50_000_000})] + #[pallet::weight(T::WeightInfo::reset_members(members.len().unique_saturated_into()))] pub fn reset_members(origin: OriginFor, members: Vec) -> DispatchResult { T::ResetOrigin::ensure_origin(origin)?; @@ -267,56 +276,65 @@ pub mod pallet { /// /// 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 { + #[pallet::weight(T::WeightInfo::change_key(T::MaxMembers::get()))] + pub fn change_key( + origin: OriginFor, + new: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { let remove = ensure_signed(origin)?; let new = T::Lookup::lookup(new)?; - if remove != new { - let mut members = >::get(); - let location = - members.binary_search(&remove).ok().ok_or(Error::::NotMember)?; - let _ = members.binary_search(&new).err().ok_or(Error::::AlreadyMember)?; - members[location] = new.clone(); - members.sort(); - - >::put(&members); - - T::MembershipChanged::change_members_sorted( - &[new.clone()], - &[remove.clone()], - &members[..], - ); - - if Prime::::get() == Some(remove) { - Prime::::put(&new); - T::MembershipChanged::set_prime(Some(new)); - } + if remove == new { + return Ok(().into()); + } + + let mut members = >::get(); + let members_length = members.len() as u32; + let location = members.binary_search(&remove).ok().ok_or(Error::::NotMember)?; + let _ = members.binary_search(&new).err().ok_or(Error::::AlreadyMember)?; + members[location] = new.clone(); + members.sort(); + + >::put(&members); + + T::MembershipChanged::change_members_sorted( + &[new.clone()], + &[remove.clone()], + &members[..], + ); + + if Prime::::get() == Some(remove) { + Prime::::put(&new); + T::MembershipChanged::set_prime(Some(new)); } Self::deposit_event(Event::KeyChanged); - Ok(()) + Ok(Some(T::WeightInfo::change_key(members_length)).into()) } /// 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 { + #[pallet::weight(T::WeightInfo::set_prime(T::MaxMembers::get()))] + pub fn set_prime( + origin: OriginFor, + who: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { T::PrimeOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; - Self::members().binary_search(&who).ok().ok_or(Error::::NotMember)?; + let members = Self::members(); + members.binary_search(&who).ok().ok_or(Error::::NotMember)?; Prime::::put(&who); T::MembershipChanged::set_prime(Some(who)); - Ok(()) + Ok(Some(T::WeightInfo::set_prime(members.len() as u32)).into()) } /// Remove the prime member if it exists. /// /// May only be called from `T::PrimeOrigin`. #[pallet::call_index(6)] - #[pallet::weight({50_000_000})] + #[pallet::weight(T::WeightInfo::clear_prime())] pub fn clear_prime(origin: OriginFor) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; Prime::::kill(); @@ -442,7 +460,7 @@ mod benchmark { } // er keep the prime common between incoming and outgoing to make sure it is rejigged. - reset_member { + reset_members { let m in 1 .. T::MaxMembers::get(); let members = (1..m+1).map(|i| account("member", i, SEED)).collect::>(); @@ -500,8 +518,7 @@ mod benchmark { } clear_prime { - let m in 1 .. T::MaxMembers::get(); - let members = (0..m).map(|i| account("member", i, SEED)).collect::>(); + let members = (0..T::MaxMembers::get()).map(|i| account("member", i, SEED)).collect::>(); let prime = members.last().cloned().unwrap(); set_members::(members, None); }: { @@ -523,16 +540,12 @@ mod tests { use super::*; use crate as pallet_membership; - use sp_core::H256; - use sp_runtime::{ - bounded_vec, - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - BuildStorage, - }; + use sp_runtime::{bounded_vec, traits::BadOrigin, BuildStorage}; use frame_support::{ - assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{ConstU32, ConstU64, StorageVersion}, + assert_noop, assert_ok, assert_storage_noop, derive_impl, ord_parameter_types, + parameter_types, + traits::{ConstU32, StorageVersion}, }; use frame_system::EnsureSignedBy; @@ -553,29 +566,7 @@ mod tests { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } ord_parameter_types! { pub const One: u64 = 1; @@ -743,6 +734,17 @@ mod tests { }); } + #[test] + fn swap_member_with_identical_arguments_changes_nothing() { + new_test_ext().execute_with(|| { + assert_storage_noop!(assert_ok!(Membership::swap_member( + RuntimeOrigin::signed(3), + 10, + 10 + ))); + }); + } + #[test] fn change_key_works() { new_test_ext().execute_with(|| { @@ -772,6 +774,13 @@ mod tests { }); } + #[test] + fn change_key_with_same_caller_as_argument_changes_nothing() { + new_test_ext().execute_with(|| { + assert_storage_noop!(assert_ok!(Membership::change_key(RuntimeOrigin::signed(10), 10))); + }); + } + #[test] fn reset_members_works() { new_test_ext().execute_with(|| { diff --git a/substrate/frame/membership/src/weights.rs b/substrate/frame/membership/src/weights.rs index 18ea7fcb315a3134747743f1a913856d084e5ad6..2d18848b89ab5b55c22235282fb78b514f7ed937 100644 --- a/substrate/frame/membership/src/weights.rs +++ b/substrate/frame/membership/src/weights.rs @@ -55,10 +55,10 @@ pub trait WeightInfo { fn add_member(m: u32, ) -> Weight; fn remove_member(m: u32, ) -> Weight; fn swap_member(m: u32, ) -> Weight; - fn reset_member(m: u32, ) -> Weight; + fn reset_members(m: u32, ) -> Weight; fn change_key(m: u32, ) -> Weight; fn set_prime(m: u32, ) -> Weight; - fn clear_prime(m: u32, ) -> Weight; + fn clear_prime() -> Weight; } /// Weights for pallet_membership using the Substrate node and recommended hardware. @@ -142,7 +142,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: TechnicalCommittee Prime (r:0 w:1) /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. - fn reset_member(m: u32, ) -> Weight { + fn reset_members(m: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `312 + m * (64 ±0)` // Estimated: `4687 + m * (64 ±0)` @@ -200,15 +200,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) /// Storage: TechnicalCommittee Prime (r:0 w:1) /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `m` is `[1, 100]`. - fn clear_prime(m: u32, ) -> Weight { + fn clear_prime() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 3_373_000 picoseconds. Weight::from_parts(3_750_452, 0) - // Standard Error: 142 - .saturating_add(Weight::from_parts(505, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().writes(2_u64)) } } @@ -293,7 +290,7 @@ impl WeightInfo for () { /// Storage: TechnicalCommittee Prime (r:0 w:1) /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. - fn reset_member(m: u32, ) -> Weight { + fn reset_members(m: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `312 + m * (64 ±0)` // Estimated: `4687 + m * (64 ±0)` @@ -351,15 +348,12 @@ impl WeightInfo for () { /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) /// Storage: TechnicalCommittee Prime (r:0 w:1) /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) - /// The range of component `m` is `[1, 100]`. - fn clear_prime(m: u32, ) -> Weight { + fn clear_prime() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 3_373_000 picoseconds. Weight::from_parts(3_750_452, 0) - // Standard Error: 142 - .saturating_add(Weight::from_parts(505, 0).saturating_mul(m.into())) .saturating_add(RocksDbWeight::get().writes(2_u64)) } } diff --git a/substrate/frame/merkle-mountain-range/Cargo.toml b/substrate/frame/merkle-mountain-range/Cargo.toml index 607fa340e9695ea1f66145e27e7ba0ac3183908d..d623e25cec2613d590562e5b17d077a10a833357 100644 --- a/substrate/frame/merkle-mountain-range/Cargo.toml +++ b/substrate/frame/merkle-mountain-range/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs index 536faa68e4e9f05f8d11941bb5cd81fe48e1948f..93fefe910e45df386d0464b455e6f55c44aaa8e0 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs @@ -30,7 +30,7 @@ pub type NodeOf = Node<>::Hashing, L>; pub type Node = DataOrHash; /// Default Merging & Hashing behavior for MMR. -pub struct Hasher(sp_std::marker::PhantomData<(H, L)>); +pub struct Hasher(core::marker::PhantomData<(H, L)>); impl mmr_lib::Merge for Hasher { type Item = Node; diff --git a/substrate/frame/message-queue/Cargo.toml b/substrate/frame/message-queue/Cargo.toml index c2ecf4452627d36a4fdac3cf05d9758bb6a47701..8d9da7df39ea58ad1f91d45e5db1bc026031ed89 100644 --- a/substrate/frame/message-queue/Cargo.toml +++ b/substrate/frame/message-queue/Cargo.toml @@ -14,8 +14,8 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +log = { workspace = true } environmental = { version = "1.1.4", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } diff --git a/substrate/frame/message-queue/src/integration_test.rs b/substrate/frame/message-queue/src/integration_test.rs index aa6f019d650df0f2d97be7b3584dba706eb686f7..cc3da6ebdc669e5710867aee63252a4bf5ca6889 100644 --- a/substrate/frame/message-queue/src/integration_test.rs +++ b/substrate/frame/message-queue/src/integration_test.rs @@ -37,14 +37,9 @@ use crate::{ }; use crate as pallet_message_queue; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; +use frame_support::{derive_impl, parameter_types}; use rand::{rngs::StdRng, Rng, SeedableRng}; use rand_distr::Pareto; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use std::collections::{BTreeMap, BTreeSet}; type Block = frame_system::mocking::MockBlock; @@ -59,29 +54,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { diff --git a/substrate/frame/message-queue/src/mock.rs b/substrate/frame/message-queue/src/mock.rs index f1067ed961e8adfd0b206a480a6d76bda32428a2..a46fa31df3e20d302650e68a26d8bcb96e5496a2 100644 --- a/substrate/frame/message-queue/src/mock.rs +++ b/substrate/frame/message-queue/src/mock.rs @@ -23,15 +23,8 @@ pub use super::mock_helpers::*; use super::*; use crate as pallet_message_queue; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use frame_support::{derive_impl, parameter_types}; +use sp_runtime::BuildStorage; use sp_std::collections::btree_map::BTreeMap; type Block = frame_system::mocking::MockBlock; @@ -46,29 +39,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { pub const HeapSize: u32 = 24; diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index cb00b38890ea48498eab31da2ff29cbc970ccddf..d1bb01dde1a47e70587dc37ad27123a0632cdcf7 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -20,9 +20,9 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = frame-benchmarking = { default-features = false, optional = true, path = "../benchmarking" } frame-support = { default-features = false, path = "../support" } frame-system = { default-features = false, path = "../system" } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["derive"] } +serde = { features = ["derive"], workspace = true } sp-application-crypto = { default-features = false, path = "../../primitives/application-crypto" } sp-arithmetic = { default-features = false, path = "../../primitives/arithmetic" } sp-io = { default-features = false, path = "../../primitives/io" } diff --git a/substrate/frame/multisig/Cargo.toml b/substrate/frame/multisig/Cargo.toml index aefdbe855a3c4b6cee4330b14c4e3d958ddccd2b..1d2a79bdc52f2fadd6fe2aa782029936c76a28e0 100644 --- a/substrate/frame/multisig/Cargo.toml +++ b/substrate/frame/multisig/Cargo.toml @@ -26,7 +26,7 @@ sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } # third party -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/substrate/frame/multisig/src/migrations.rs b/substrate/frame/multisig/src/migrations.rs index 330613bb3dfda94224b223a2f3f1fdf6735179b0..d03e42a66a5b5af07248ee7b11061d5f26b66c98 100644 --- a/substrate/frame/multisig/src/migrations.rs +++ b/substrate/frame/multisig/src/migrations.rs @@ -39,7 +39,7 @@ pub mod v1 { (OpaqueCall, ::AccountId, BalanceOf), >; - pub struct MigrateToV1(sp_std::marker::PhantomData); + pub struct MigrateToV1(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { diff --git a/substrate/frame/nft-fractionalization/Cargo.toml b/substrate/frame/nft-fractionalization/Cargo.toml index a55bcab533d8ee05476ac6fdc0520129e6525771..8002b7e1165f1648d5edd39486c996f4eabe2bc6 100644 --- a/substrate/frame/nft-fractionalization/Cargo.toml +++ b/substrate/frame/nft-fractionalization/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/nft-fractionalization/src/mock.rs b/substrate/frame/nft-fractionalization/src/mock.rs index 40cce96d55073cd70f5368e575e12f3635bcdb24..a41386150091de0d77153593dfe6ce68a07cfe18 100644 --- a/substrate/frame/nft-fractionalization/src/mock.rs +++ b/substrate/frame/nft-fractionalization/src/mock.rs @@ -27,9 +27,8 @@ use frame_support::{ }; use frame_system::EnsureSigned; use pallet_nfts::PalletFeatures; -use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + traits::{IdentifyAccount, IdentityLookup, Verify}, BuildStorage, MultiSignature, }; @@ -52,29 +51,10 @@ construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/nfts/Cargo.toml b/substrate/frame/nfts/Cargo.toml index d92a9c0b44ed00ed127808a011586e162b1e473e..69e9ea170b14c97ef81a9a5a4fa8ae855cce94d2 100644 --- a/substrate/frame/nfts/Cargo.toml +++ b/substrate/frame/nfts/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } enumflags2 = { version = "0.7.7" } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/nfts/src/macros.rs b/substrate/frame/nfts/src/macros.rs index 1a601ce0927fa2f4c3cfae880d91e892e83d62fd..d313c8785b27149977badb4be7cefcdef8f8180e 100644 --- a/substrate/frame/nfts/src/macros.rs +++ b/substrate/frame/nfts/src/macros.rs @@ -42,7 +42,7 @@ macro_rules! impl_codec_bitflags { impl Decode for $wrapper { fn decode( input: &mut I, - ) -> sp_std::result::Result { + ) -> ::core::result::Result { let field = <$size>::decode(input)?; Ok(Self(BitFlags::from_bits(field as $size).map_err(|_| "invalid value")?)) } diff --git a/substrate/frame/nfts/src/migration.rs b/substrate/frame/nfts/src/migration.rs index 94635a96aeba991fb458f2cc7830922060727dd7..d4cdf7e820d15b7df1d8f36807baf34b81c34bd3 100644 --- a/substrate/frame/nfts/src/migration.rs +++ b/substrate/frame/nfts/src/migration.rs @@ -51,7 +51,7 @@ pub mod v1 { } /// A migration utility to update the storage version from v0 to v1 for the pallet. - pub struct MigrateToV1(sp_std::marker::PhantomData); + pub struct MigrateToV1(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { fn on_runtime_upgrade() -> Weight { let current_version = Pallet::::current_storage_version(); diff --git a/substrate/frame/nfts/src/mock.rs b/substrate/frame/nfts/src/mock.rs index ac6d0d757325d25981910354b58374a24838f3f5..e86fafd07e965ab995a04e512bd9794833735d4e 100644 --- a/substrate/frame/nfts/src/mock.rs +++ b/substrate/frame/nfts/src/mock.rs @@ -24,10 +24,9 @@ use frame_support::{ construct_runtime, derive_impl, parameter_types, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; -use sp_core::H256; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ - traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + traits::{IdentifyAccount, IdentityLookup, Verify}, BuildStorage, MultiSignature, }; @@ -48,29 +47,10 @@ pub type AccountId = ::AccountId; #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/nis/src/mock.rs b/substrate/frame/nis/src/mock.rs index 98b653635caf23b0225df697b8c7629f49447623..03976bc66c4df6844c81232fef7f912cb02b5292 100644 --- a/substrate/frame/nis/src/mock.rs +++ b/substrate/frame/nis/src/mock.rs @@ -21,19 +21,13 @@ use crate::{self as pallet_nis, Perquintill, WithMaximumOf}; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{ - fungible::Inspect, ConstU16, ConstU32, ConstU64, Everything, OnFinalize, OnInitialize, - StorageMapShim, - }, + traits::{fungible::Inspect, ConstU32, ConstU64, OnFinalize, OnInitialize, StorageMapShim}, weights::Weight, PalletId, }; use pallet_balances::{Instance1, Instance2}; -use sp_core::{ConstU128, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_core::ConstU128; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -52,29 +46,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = ConstU16<42>; - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml index ac139853cdbd322c7aaba6743077186ebaf4ebc0..a39b0ec4eff8b7fce397866acc7be3a8b812c827 100644 --- a/substrate/frame/node-authorization/Cargo.toml +++ b/substrate/frame/node-authorization/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/node-authorization/src/mock.rs b/substrate/frame/node-authorization/src/mock.rs index bd648de2b243c781c6db8c7e9375add8ee07dcf4..84ca4d7eff701118e8ec4d140d164e709cc1c8f6 100644 --- a/substrate/frame/node-authorization/src/mock.rs +++ b/substrate/frame/node-authorization/src/mock.rs @@ -20,16 +20,9 @@ use super::*; use crate as pallet_node_authorization; -use frame_support::{ - derive_impl, ord_parameter_types, - traits::{ConstU32, ConstU64}, -}; +use frame_support::{derive_impl, ord_parameter_types, traits::ConstU32}; use frame_system::EnsureSignedBy; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -43,29 +36,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type DbWeight = (); - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } ord_parameter_types! { diff --git a/substrate/frame/node-authorization/src/weights.rs b/substrate/frame/node-authorization/src/weights.rs index a4529c845c7c0e4b15a542cdaa07fc01e5279efe..881eeaf7a4c090573eab463494fd9a6846ae4b40 100644 --- a/substrate/frame/node-authorization/src/weights.rs +++ b/substrate/frame/node-authorization/src/weights.rs @@ -22,7 +22,7 @@ #![allow(unused_imports)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; pub trait WeightInfo { fn add_well_known_node() -> Weight; diff --git a/substrate/frame/nomination-pools/Cargo.toml b/substrate/frame/nomination-pools/Cargo.toml index cac092c98dc400815ef3a8b221b43e6a948be888..9830f31d5fa9accedeb5edbde0c526df4ecefee2 100644 --- a/substrate/frame/nomination-pools/Cargo.toml +++ b/substrate/frame/nomination-pools/Cargo.toml @@ -31,7 +31,7 @@ sp-std = { path = "../../primitives/std", default-features = false } sp-staking = { path = "../../primitives/staking", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } sp-io = { path = "../../primitives/io", default-features = false } -log = { version = "0.4.0", default-features = false } +log = { workspace = true } # Optional: use for testing and/or fuzzing pallet-balances = { path = "../balances", optional = true } diff --git a/substrate/frame/nomination-pools/fuzzer/Cargo.toml b/substrate/frame/nomination-pools/fuzzer/Cargo.toml index 52f49b28457c26c3c247dd4c137565cd9564dcc8..c0d63a2685937a10e6a53288403099bc78ed1fa9 100644 --- a/substrate/frame/nomination-pools/fuzzer/Cargo.toml +++ b/substrate/frame/nomination-pools/fuzzer/Cargo.toml @@ -29,7 +29,7 @@ sp-io = { path = "../../../primitives/io" } sp-tracing = { path = "../../../primitives/tracing" } rand = { version = "0.8.5", features = ["small_rng"] } -log = "0.4.17" +log = { workspace = true, default-features = true } [[bin]] name = "call" diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs index c2653d8bdabcc5a0bcc9378bd300252faf829ff6..074d59931ade733a3199e227a99c6dff819be793 100644 --- a/substrate/frame/nomination-pools/src/lib.rs +++ b/substrate/frame/nomination-pools/src/lib.rs @@ -1293,27 +1293,6 @@ impl BondedPool { }); }; } - - /// Withdraw all the funds that are already unlocked from staking for the - /// [`BondedPool::bonded_account`]. - /// - /// Also reduces the [`TotalValueLocked`] by the difference of the - /// [`T::Staking::total_stake`] of the [`BondedPool::bonded_account`] that might occur by - /// [`T::Staking::withdraw_unbonded`]. - /// - /// Returns the result of [`T::Staking::withdraw_unbonded`] - fn withdraw_from_staking(&self, num_slashing_spans: u32) -> Result { - let bonded_account = self.bonded_account(); - - let prev_total = T::Staking::total_stake(&bonded_account.clone()).unwrap_or_default(); - let outcome = T::Staking::withdraw_unbonded(bonded_account.clone(), num_slashing_spans); - let diff = prev_total - .defensive_saturating_sub(T::Staking::total_stake(&bonded_account).unwrap_or_default()); - TotalValueLocked::::mutate(|tvl| { - tvl.saturating_reduce(diff); - }); - outcome - } } /// A reward pool. @@ -1733,7 +1712,7 @@ pub mod pallet { CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner>; /// Reward pools. This is where there rewards for each pool accumulate. When a members payout is - /// claimed, the balance comes out fo the reward pool. Keyed by the bonded pools account. + /// claimed, the balance comes out of the reward pool. Keyed by the bonded pools account. #[pallet::storage] pub type RewardPools = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; @@ -1753,8 +1732,8 @@ pub mod pallet { /// A reverse lookup from the pool's account id to its id. /// - /// This is only used for slashing. In all other instances, the pool id is used, and the - /// accounts are deterministically derived from it. + /// This is only used for slashing and on automatic withdraw update. In all other instances, the + /// pool id is used, and the accounts are deterministically derived from it. #[pallet::storage] pub type ReversePoolIdLookup = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>; @@ -2223,7 +2202,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); - pool.withdraw_from_staking(num_slashing_spans)?; + T::Staking::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?; Ok(()) } @@ -2275,7 +2254,8 @@ pub mod pallet { // Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the // `transferrable_balance` is correct. - let stash_killed = bonded_pool.withdraw_from_staking(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. @@ -3628,4 +3608,14 @@ impl sp_staking::OnStakingUpdate> for Pall } Self::deposit_event(Event::::PoolSlashed { pool_id, balance: slashed_bonded }); } + + /// Reduces the overall `TotalValueLocked` if a withdrawal happened for a pool involved in the + /// staking withdraw. + fn on_withdraw(pool_account: &T::AccountId, amount: BalanceOf) { + if ReversePoolIdLookup::::get(pool_account).is_some() { + TotalValueLocked::::mutate(|tvl| { + tvl.saturating_reduce(amount); + }); + } + } } diff --git a/substrate/frame/nomination-pools/src/migration.rs b/substrate/frame/nomination-pools/src/migration.rs index 559baf76e4c64c5893d44c15d3505a9083ed76db..6887fcfa7ecad36172c17e36d55713df27865482 100644 --- a/substrate/frame/nomination-pools/src/migration.rs +++ b/substrate/frame/nomination-pools/src/migration.rs @@ -56,6 +56,59 @@ pub mod versioned { >; } +pub mod unversioned { + use super::*; + + /// Checks and updates `TotalValueLocked` if out of sync. + pub struct TotalValueLockedSync(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for TotalValueLockedSync { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + let migrated = BondedPools::::count(); + + // recalcuate the `TotalValueLocked` to compare with the current on-chain TVL which may + // be out of sync. + let tvl: BalanceOf = helpers::calculate_tvl_by_total_stake::(); + let onchain_tvl = TotalValueLocked::::get(); + + let writes = if tvl != onchain_tvl { + TotalValueLocked::::set(tvl); + + log!( + info, + "on-chain TVL was out of sync, update. Old: {:?}, new: {:?}", + onchain_tvl, + tvl + ); + + // writes: onchain version + set total value locked. + 2 + } else { + log!(info, "on-chain TVL was OK: {:?}", tvl); + + // writes: onchain version write. + 1 + }; + + // reads: migrated * (BondedPools + Staking::total_stake) + count + onchain + // version + // + // writes: current version + (maybe) TVL + T::DbWeight::get() + .reads_writes(migrated.saturating_mul(2).saturating_add(2).into(), writes) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { + Ok(()) + } + } +} + pub mod v8 { use super::{v7::V7BondedPoolInner, *}; @@ -146,6 +199,7 @@ pub(crate) mod v7 { } impl V7BondedPool { + #[allow(dead_code)] fn bonded_account(&self) -> T::AccountId { Pallet::::create_bonded_account(self.id) } @@ -157,26 +211,12 @@ pub(crate) mod v7 { CountedStorageMap, Twox64Concat, PoolId, V7BondedPoolInner>; pub struct VersionUncheckedMigrateV6ToV7(sp_std::marker::PhantomData); - impl VersionUncheckedMigrateV6ToV7 { - fn calculate_tvl_by_total_stake() -> BalanceOf { - BondedPools::::iter() - .map(|(id, inner)| { - T::Staking::total_stake( - &V7BondedPool { id, inner: inner.clone() }.bonded_account(), - ) - .unwrap_or_default() - }) - .reduce(|acc, total_balance| acc + total_balance) - .unwrap_or_default() - } - } - impl OnRuntimeUpgrade for VersionUncheckedMigrateV6ToV7 { fn on_runtime_upgrade() -> Weight { let migrated = BondedPools::::count(); // The TVL should be the sum of all the funds that are actively staked and in the // unbonding process of the account of each pool. - let tvl: BalanceOf = Self::calculate_tvl_by_total_stake(); + let tvl: BalanceOf = helpers::calculate_tvl_by_total_stake::(); TotalValueLocked::::set(tvl); @@ -198,7 +238,7 @@ pub(crate) mod v7 { fn post_upgrade(_data: Vec) -> Result<(), TryRuntimeError> { // check that the `TotalValueLocked` written is actually the sum of `total_stake` of the // `BondedPools`` - let tvl: BalanceOf = Self::calculate_tvl_by_total_stake(); + let tvl: BalanceOf = helpers::calculate_tvl_by_total_stake::(); ensure!( TotalValueLocked::::get() == tvl, "TVL written is not equal to `Staking::total_stake` of all `BondedPools`." @@ -977,3 +1017,17 @@ pub mod v1 { } } } + +mod helpers { + use super::*; + + pub(crate) fn calculate_tvl_by_total_stake() -> BalanceOf { + BondedPools::::iter() + .map(|(id, inner)| { + T::Staking::total_stake(&BondedPool { id, inner: inner.clone() }.bonded_account()) + .unwrap_or_default() + }) + .reduce(|acc, total_balance| acc + total_balance) + .unwrap_or_default() + } +} diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs index 84c41a15b201052cd03b3c67fff4cb0c8980a92f..f982b72c63564c0cff6106ded6ad934fec10f48c 100644 --- a/substrate/frame/nomination-pools/src/mock.rs +++ b/substrate/frame/nomination-pools/src/mock.rs @@ -134,11 +134,24 @@ impl sp_staking::StakingInterface for StakingMock { fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { let mut unbonding_map = UnbondingBalanceMap::get(); + + // closure to calculate the current unlocking funds across all eras/accounts. + let unlocking = |pair: &Vec<(EraIndex, Balance)>| -> Balance { + pair.iter() + .try_fold(Zero::zero(), |acc: Balance, (_at, amount)| acc.checked_add(*amount)) + .unwrap() + }; + let staker_map = unbonding_map.get_mut(&who).ok_or("Nothing to unbond")?; + let unlocking_before = unlocking(&staker_map); let current_era = Self::current_era(); + staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era); + // if there was a withdrawal, notify the pallet. + Pools::on_withdraw(&who, unlocking_before.saturating_sub(unlocking(&staker_map))); + UnbondingBalanceMap::set(&unbonding_map); Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty()) } diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs index 47f48ba98b7fdbba57b5e827e95b811f5adacacb..13298fa64f54c0439384fcb8fba7b512678ff687 100644 --- a/substrate/frame/nomination-pools/src/tests.rs +++ b/substrate/frame/nomination-pools/src/tests.rs @@ -23,7 +23,7 @@ use sp_runtime::{bounded_btree_map, traits::Dispatchable, FixedU128}; macro_rules! unbonding_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ - use sp_std::iter::{Iterator, IntoIterator}; + use ::core::iter::{Iterator, IntoIterator}; let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])); BoundedBTreeMap::, TotalUnbondingPools>::try_from(not_bounded).unwrap() }}; diff --git a/substrate/frame/nomination-pools/test-staking/Cargo.toml b/substrate/frame/nomination-pools/test-staking/Cargo.toml index 845535ae04f567bbe5fa4c4c7f22854b077176b5..9c7b12e4c6345b2080ef4b989171929c505c8a40 100644 --- a/substrate/frame/nomination-pools/test-staking/Cargo.toml +++ b/substrate/frame/nomination-pools/test-staking/Cargo.toml @@ -37,4 +37,4 @@ pallet-staking-reward-curve = { path = "../../staking/reward-curve" } pallet-nomination-pools = { path = ".." } sp-tracing = { path = "../../../primitives/tracing" } -log = { version = "0.4.0" } +log = { workspace = true, default-features = true } diff --git a/substrate/frame/nomination-pools/test-staking/src/mock.rs b/substrate/frame/nomination-pools/test-staking/src/mock.rs index 873a59bb3afa65bbd65ad0c8e367d187d48fa69b..1afc37bf07e136bd303a4f6c5c100554f4f819c3 100644 --- a/substrate/frame/nomination-pools/test-staking/src/mock.rs +++ b/substrate/frame/nomination-pools/test-staking/src/mock.rs @@ -236,6 +236,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, )); }); diff --git a/substrate/frame/offences/Cargo.toml b/substrate/frame/offences/Cargo.toml index 73842c696af294adb729c7621e239fe7dcd7e4d8..b3ef4671ce56f4b66d7e7cb4ce7ec7faec05d3fe 100644 --- a/substrate/frame/offences/Cargo.toml +++ b/substrate/frame/offences/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } pallet-balances = { path = "../balances", default-features = false } diff --git a/substrate/frame/offences/benchmarking/Cargo.toml b/substrate/frame/offences/benchmarking/Cargo.toml index fc3fb076f1f9ecf4652cfa64f3daa7ca1e5d4478..8dcce84d257e596609e4062f8a46d684a72b2683 100644 --- a/substrate/frame/offences/benchmarking/Cargo.toml +++ b/substrate/frame/offences/benchmarking/Cargo.toml @@ -32,7 +32,7 @@ pallet-staking = { path = "../../staking", default-features = false } sp-runtime = { path = "../../../primitives/runtime", default-features = false } sp-staking = { path = "../../../primitives/staking", default-features = false } sp-std = { path = "../../../primitives/std", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-staking-reward-curve = { path = "../../staking/reward-curve" } diff --git a/substrate/frame/parameters/Cargo.toml b/substrate/frame/parameters/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..07ebeea52d5298be0db8b00c09ab73641d20080b --- /dev/null +++ b/substrate/frame/parameters/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "pallet-parameters" +description = "Pallet to store and configure parameters." +repository.workspace = true +license = "Apache-2.0" +version = "0.0.1" +authors = ["Acala Developers", "Parity Technologies "] +edition.workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +paste = { version = "1.0.14", default-features = false } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } +docify = "0.2.5" + +frame-support = { path = "../support", default-features = false, features = ["experimental"] } +frame-system = { path = "../system", default-features = false } +sp-core = { path = "../../primitives/core", default-features = false } +sp-runtime = { path = "../../primitives/runtime", default-features = false } +sp-std = { path = "../../primitives/std", default-features = false } +frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } + +[dev-dependencies] +sp-core = { path = "../../primitives/core", features = ["std"] } +sp-io = { path = "../../primitives/io", features = ["std"] } +pallet-example-basic = { path = "../examples/basic", features = ["std"] } +pallet-balances = { path = "../balances", features = ["std"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-example-basic/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-example-basic/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/parameters/src/benchmarking.rs b/substrate/frame/parameters/src/benchmarking.rs new file mode 100644 index 0000000000000000000000000000000000000000..1f22e026cbca07b236c4680570a3312d8c4b85e5 --- /dev/null +++ b/substrate/frame/parameters/src/benchmarking.rs @@ -0,0 +1,51 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Parameters pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +#[cfg(test)] +use crate::Pallet as Parameters; + +use frame_benchmarking::v2::*; + +#[benchmarks(where T::RuntimeParameters: Default)] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_parameter() -> Result<(), BenchmarkError> { + let kv = T::RuntimeParameters::default(); + let k = kv.clone().into_parts().0; + + let origin = + T::AdminOrigin::try_successful_origin(&k).map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, kv); + + Ok(()) + } + + impl_benchmark_test_suite! { + Parameters, + crate::tests::mock::new_test_ext(), + crate::tests::mock::Runtime, + } +} diff --git a/substrate/frame/parameters/src/lib.rs b/substrate/frame/parameters/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..91cf10ba93f7f9ebf03363cfe3bf8a9b4ff89172 --- /dev/null +++ b/substrate/frame/parameters/src/lib.rs @@ -0,0 +1,270 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] +#![deny(missing_docs)] +// Need to enable this one since we document feature-gated stuff. +#![allow(rustdoc::broken_intra_doc_links)] + +//! # **⚠️ WARNING ⚠️** +//! +//!
+//! THIS CRATE IS NOT AUDITED AND SHOULD NOT BE USED IN PRODUCTION. +//!
+//! +//! # Parameters +//! +//! Allows to update configuration parameters at runtime. +//! +//! ## Pallet API +//! +//! This pallet exposes two APIs; one *inbound* side to update parameters, and one *outbound* side +//! to access said parameters. Parameters themselves are defined in the runtime config and will be +//! aggregated into an enum. Each parameter is addressed by a `key` and can have a default value. +//! This is not done by the pallet but through the [`frame_support::dynamic_params::dynamic_params`] +//! macro or alternatives. +//! +//! Note that this is incurring one storage read per access. This should not be a problem in most +//! cases but must be considered in weight-restrained code. +//! +//! ### Inbound +//! +//! The inbound side solely consists of the [`Pallet::set_parameter`] extrinsic to update the value +//! of a parameter. Each parameter can have their own admin origin as given by the +//! [`Config::AdminOrigin`]. +//! +//! ### Outbound +//! +//! The outbound side is runtime facing for the most part. More general, it provides a `Get` +//! implementation and can be used in every spot where that is accepted. Two macros are in place: +//! [`frame_support::dynamic_params::define_parameters` and +//! [`frame_support::dynamic_params:dynamic_pallet_params`] to define and expose parameters in a +//! typed manner. +//! +//! See the [`pallet`] module for more information about the interfaces this pallet exposes, +//! including its configuration trait, dispatchables, storage items, events and errors. +//! +//! ## Overview +//! +//! This pallet is a good fit for updating parameters without a runtime upgrade. It is very handy to +//! not require a runtime upgrade for a simple parameter change since runtime upgrades require a lot +//! of diligence and always bear risks. It seems overkill to update the whole runtime for a simple +//! parameter change. This pallet allows for fine-grained control over who can update what. +//! The only down-side is that it trades off performance with convenience and should therefore only +//! be used in places where that is proven to be uncritical. Values that are rarely accessed but +//! change often would be a perfect fit. +//! +//! ### Example Configuration +//! +//! Here is an example of how to define some parameters, including their default values: +#![doc = docify::embed!("src/tests/mock.rs", dynamic_params)] +//! +//! A permissioned origin can be define on a per-key basis like this: +#![doc = docify::embed!("src/tests/mock.rs", custom_origin)] +//! +//! The pallet will also require a default value for benchmarking. Ideally this is the variant with +//! the longest encoded length. Although in either case the PoV benchmarking will take the worst +//! case over the whole enum. +#![doc = docify::embed!("src/tests/mock.rs", benchmarking_default)] +//! +//! Now the aggregated parameter needs to be injected into the pallet config: +#![doc = docify::embed!("src/tests/mock.rs", impl_config)] +//! +//! As last step, the parameters can now be used in other pallets 🙌 +#![doc = docify::embed!("src/tests/mock.rs", usage)] +//! +//! ### Examples Usage +//! +//! Now to demonstrate how the values can be updated: +#![doc = docify::embed!("src/tests/unit.rs", set_parameters_example)] +//! +//! ## Low Level / Implementation Details +//! +//! The pallet stores the parameters in a storage map and implements the matching `Get` for +//! each `Key` type. The `Get` then accesses the `Parameters` map to retrieve the value. An event is +//! emitted every time that a value was updated. It is even emitted when the value is changed to the +//! same. +//! +//! The key and value types themselves are defined by macros and aggregated into a runtime wide +//! enum. This enum is then injected into the pallet. This allows it to be used without any changes +//! to the pallet that the parameter will be utilized by. +//! +//! ### Design Goals +//! +//! 1. Easy to update without runtime upgrade. +//! 2. Exposes metadata and docs for user convenience. +//! 3. Can be permissioned on a per-key base. +//! +//! ### Design +//! +//! 1. Everything is done at runtime without the need for `const` values. `Get` allows for this - +//! which is coincidentally an upside and a downside. 2. The types are defined through macros, which +//! allows to expose metadata and docs. 3. Access control is done through the `EnsureOriginWithArg` +//! trait, that allows to pass data along to the origin check. It gets passed in the key. The +//! implementor can then match on the key and the origin to decide whether the origin is +//! permissioned to set the value. + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +use frame_support::traits::{ + dynamic_params::{AggregratedKeyValue, IntoKey, Key, RuntimeParameterStore, TryIntoKey}, + EnsureOriginWithArg, +}; + +mod benchmarking; +#[cfg(test)] +mod tests; +mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// The key type of a parameter. +type KeyOf = <::RuntimeParameters as AggregratedKeyValue>::Key; + +/// The value type of a parameter. +type ValueOf = <::RuntimeParameters as AggregratedKeyValue>::Value; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + /// The overarching event type. + #[pallet::no_default_bounds] + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The overarching KV type of the parameters. + /// + /// Usually created by [`frame_support::dynamic_params`] or equivalent. + #[pallet::no_default_bounds] + type RuntimeParameters: AggregratedKeyValue; + + /// The origin which may update a parameter. + /// + /// The key of the parameter is passed in as second argument to allow for fine grained + /// control. + #[pallet::no_default_bounds] + type AdminOrigin: EnsureOriginWithArg>; + + /// Weight information for extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// A Parameter was set. + /// + /// Is also emitted when the value was not changed. + Updated { + /// The key that was updated. + key: ::Key, + /// The old value before this call. + old_value: Option<::Value>, + /// The new value after this call. + new_value: Option<::Value>, + }, + } + + /// Stored parameters. + #[pallet::storage] + pub type Parameters = + StorageMap<_, Blake2_128Concat, KeyOf, ValueOf, OptionQuery>; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + /// Set the value of a parameter. + /// + /// The dispatch origin of this call must be `AdminOrigin` for the given `key`. Values be + /// deleted by setting them to `None`. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::set_parameter())] + pub fn set_parameter( + origin: OriginFor, + key_value: T::RuntimeParameters, + ) -> DispatchResult { + let (key, new) = key_value.into_parts(); + T::AdminOrigin::ensure_origin(origin, &key)?; + + let mut old = None; + Parameters::::mutate(&key, |v| { + old = v.clone(); + *v = new.clone(); + }); + + Self::deposit_event(Event::Updated { key, old_value: old, new_value: new }); + + Ok(()) + } + } + /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`]. + pub mod config_preludes { + use super::*; + use frame_support::derive_impl; + + /// A configuration for testing. + pub struct TestDefaultConfig; + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)] + impl frame_system::DefaultConfig for TestDefaultConfig {} + + #[frame_support::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + #[inject_runtime_type] + type RuntimeEvent = (); + #[inject_runtime_type] + type RuntimeParameters = (); + + type AdminOrigin = frame_support::traits::AsEnsureOriginWithArg< + frame_system::EnsureRoot, + >; + + type WeightInfo = (); + } + } +} + +impl RuntimeParameterStore for Pallet { + type AggregratedKeyValue = T::RuntimeParameters; + + fn get(key: K) -> Option + where + KV: AggregratedKeyValue, + K: Key + Into<::Key>, + ::Key: IntoKey< + <::AggregratedKeyValue as AggregratedKeyValue>::Key, + >, + <::AggregratedKeyValue as AggregratedKeyValue>::Value: + TryIntoKey<::Value>, + ::Value: TryInto, + { + let key: ::Key = key.into(); + let val = Parameters::::get(key.into_key()); + val.and_then(|v| { + let val: ::Value = v.try_into_key().ok()?; + let val: K::WrappedValue = val.try_into().ok()?; + let val = val.into(); + Some(val) + }) + } +} diff --git a/substrate/frame/parameters/src/tests/mock.rs b/substrate/frame/parameters/src/tests/mock.rs new file mode 100644 index 0000000000000000000000000000000000000000..98612dc6a6d959c91b78570f562bce9074c43f61 --- /dev/null +++ b/substrate/frame/parameters/src/tests/mock.rs @@ -0,0 +1,152 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(any(test, feature = "runtime-benchmarks"))] + +//! Mock runtime that configures the `pallet_example_basic` to use dynamic params for testing. + +use frame_support::{ + construct_runtime, derive_impl, + dynamic_params::{dynamic_pallet_params, dynamic_params}, + traits::EnsureOriginWithArg, +}; + +use crate as pallet_parameters; +use crate::*; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = frame_system::mocking::MockBlock; + type AccountData = pallet_balances::AccountData<::Balance>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for Runtime { + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +#[docify::export] +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 3)] + pub mod pallet1 { + #[codec(index = 0)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 1; + #[codec(index = 2)] + pub static Key3: u128 = 2; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod pallet2 { + #[codec(index = 2)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 2; + #[codec(index = 0)] + pub static Key3: u128 = 4; + } +} + +#[docify::export(benchmarking_default)] +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters::Pallet1(dynamic_params::pallet1::Parameters::Key1( + dynamic_params::pallet1::Key1, + Some(123), + )) + } +} + +#[docify::export] +mod custom_origin { + use super::*; + pub struct ParamsManager; + + impl EnsureOriginWithArg for ParamsManager { + type Success = (); + + fn try_origin( + origin: RuntimeOrigin, + key: &RuntimeParametersKey, + ) -> Result { + // Account 123 is allowed to set parameters in benchmarking only: + #[cfg(feature = "runtime-benchmarks")] + if ensure_signed(origin.clone()).map_or(false, |acc| acc == 123) { + return Ok(()); + } + + match key { + RuntimeParametersKey::Pallet1(_) => ensure_root(origin.clone()), + RuntimeParametersKey::Pallet2(_) => ensure_signed(origin.clone()).map(|_| ()), + } + .map_err(|_| origin) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { + Ok(RuntimeOrigin::signed(123)) + } + } +} + +#[docify::export(impl_config)] +#[derive_impl(pallet_parameters::config_preludes::TestDefaultConfig as pallet_parameters::DefaultConfig)] +impl Config for Runtime { + type AdminOrigin = custom_origin::ParamsManager; + // RuntimeParameters is injected by the `derive_impl` macro. + // RuntimeEvent is injected by the `derive_impl` macro. + // WeightInfo is injected by the `derive_impl` macro. +} + +#[docify::export(usage)] +impl pallet_example_basic::Config for Runtime { + // Use the dynamic key in the pallet config: + type MagicNumber = dynamic_params::pallet1::Key1; + + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + PalletParameters: crate, + Example: pallet_example_basic, + Balances: pallet_balances, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub(crate) fn assert_last_event(generic_event: RuntimeEvent) { + let events = frame_system::Pallet::::events(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events.last().expect("Event expected"); + assert_eq!(event, &generic_event); +} diff --git a/substrate/frame/parameters/src/tests/mod.rs b/substrate/frame/parameters/src/tests/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..0a2d16906aadd394c5facb8c2bdbf45195574246 --- /dev/null +++ b/substrate/frame/parameters/src/tests/mod.rs @@ -0,0 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) mod mock; +mod test_renamed; +mod unit; diff --git a/substrate/frame/parameters/src/tests/test_renamed.rs b/substrate/frame/parameters/src/tests/test_renamed.rs new file mode 100644 index 0000000000000000000000000000000000000000..b2e0c1fd9661b4122663a4633e1d4500ac6fad1c --- /dev/null +++ b/substrate/frame/parameters/src/tests/test_renamed.rs @@ -0,0 +1,168 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(any(test, feature = "runtime-benchmarks"))] + +//! Tests that the runtime params can be renamed. + +use frame_support::{ + assert_noop, assert_ok, construct_runtime, derive_impl, + dynamic_params::{dynamic_pallet_params, dynamic_params}, + traits::AsEnsureOriginWithArg, +}; +use frame_system::EnsureRoot; + +use crate as pallet_parameters; +use crate::*; +use dynamic_params::*; +use RuntimeParametersRenamed::*; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = frame_system::mocking::MockBlock; + type AccountData = pallet_balances::AccountData<::Balance>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for Runtime { + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +#[dynamic_params(RuntimeParametersRenamed, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 3)] + pub mod pallet1 { + #[codec(index = 0)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 1; + #[codec(index = 2)] + pub static Key3: u128 = 2; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod pallet2 { + #[codec(index = 2)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 2; + #[codec(index = 0)] + pub static Key3: u128 = 4; + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParametersRenamed { + fn default() -> Self { + RuntimeParametersRenamed::Pallet1(dynamic_params::pallet1::Parameters::Key1( + dynamic_params::pallet1::Key1, + Some(123), + )) + } +} + +#[derive_impl(pallet_parameters::config_preludes::TestDefaultConfig as pallet_parameters::DefaultConfig)] +impl Config for Runtime { + type AdminOrigin = AsEnsureOriginWithArg>; + type RuntimeParameters = RuntimeParametersRenamed; + // RuntimeEvent is injected by the `derive_impl` macro. + // WeightInfo is injected by the `derive_impl` macro. +} + +impl pallet_example_basic::Config for Runtime { + // Use the dynamic key in the pallet config: + type MagicNumber = dynamic_params::pallet1::Key1; + + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + PalletParameters: crate, + Example: pallet_example_basic, + Balances: pallet_balances, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub(crate) fn assert_last_event(generic_event: RuntimeEvent) { + let events = frame_system::Pallet::::events(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events.last().expect("Event expected"); + assert_eq!(event, &generic_event); +} + +#[test] +fn set_parameters_example() { + new_test_ext().execute_with(|| { + assert_eq!(pallet1::Key3::get(), 2, "Default works"); + + // This gets rejected since the origin is not root. + assert_noop!( + PalletParameters::set_parameter( + RuntimeOrigin::signed(1), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + ), + DispatchError::BadOrigin + ); + + assert_ok!(PalletParameters::set_parameter( + RuntimeOrigin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + assert_last_event( + crate::Event::Updated { + key: RuntimeParametersRenamedKey::Pallet1(pallet1::ParametersKey::Key3( + pallet1::Key3, + )), + old_value: None, + new_value: Some(RuntimeParametersRenamedValue::Pallet1( + pallet1::ParametersValue::Key3(123), + )), + } + .into(), + ); + }); +} + +#[test] +fn get_through_external_pallet_works() { + new_test_ext().execute_with(|| { + assert_eq!(::MagicNumber::get(), 0); + + assert_ok!(PalletParameters::set_parameter( + RuntimeOrigin::root(), + Pallet1(pallet1::Parameters::Key1(pallet1::Key1, Some(123))), + )); + + assert_eq!(::MagicNumber::get(), 123); + }); +} diff --git a/substrate/frame/parameters/src/tests/unit.rs b/substrate/frame/parameters/src/tests/unit.rs new file mode 100644 index 0000000000000000000000000000000000000000..d3f11ba96403514fbe77ba89f57872e85cbf655a --- /dev/null +++ b/substrate/frame/parameters/src/tests/unit.rs @@ -0,0 +1,311 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Unit tests for the parameters pallet. + +#![cfg(test)] + +use crate::tests::mock::{ + assert_last_event, dynamic_params::*, new_test_ext, PalletParameters, Runtime, + RuntimeOrigin as Origin, RuntimeParameters, RuntimeParameters::*, RuntimeParametersKey, + RuntimeParametersValue, +}; +use codec::Encode; +use frame_support::{assert_noop, assert_ok, traits::dynamic_params::AggregratedKeyValue}; +use sp_core::Get; +use sp_runtime::DispatchError; + +#[docify::export] +#[test] +fn set_parameters_example() { + new_test_ext().execute_with(|| { + assert_eq!(pallet1::Key3::get(), 2, "Default works"); + + // This gets rejected since the origin is not root. + assert_noop!( + PalletParameters::set_parameter( + Origin::signed(1), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + ), + DispatchError::BadOrigin + ); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + assert_last_event( + crate::Event::Updated { + key: RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)), + old_value: None, + new_value: Some(RuntimeParametersValue::Pallet1(pallet1::ParametersValue::Key3( + 123, + ))), + } + .into(), + ); + }); +} + +#[test] +fn set_parameters_same_is_noop() { + new_test_ext().execute_with(|| { + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + }); +} + +#[test] +fn set_parameters_twice_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(432))), + )); + + assert_eq!(pallet1::Key3::get(), 432, "Update works"); + }); +} + +#[test] +fn set_parameters_removing_restores_default_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + assert!( + crate::Parameters::::contains_key(RuntimeParametersKey::Pallet1( + pallet1::ParametersKey::Key3(pallet1::Key3) + )), + "Key inserted" + ); + + // Removing the value restores the default. + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, None)), + )); + + assert_eq!(pallet1::Key3::get(), 2, "Default restored"); + assert!( + !crate::Parameters::::contains_key(RuntimeParametersKey::Pallet1( + pallet1::ParametersKey::Key3(pallet1::Key3) + )), + "Key removed" + ); + }); +} + +#[test] +fn set_parameters_to_default_emits_events_works() { + new_test_ext().execute_with(|| { + assert_eq!(pallet1::Key3::get(), 2); + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(2))), + )); + assert_eq!(pallet1::Key3::get(), 2); + + assert!( + crate::Parameters::::contains_key(RuntimeParametersKey::Pallet1( + pallet1::ParametersKey::Key3(pallet1::Key3) + )), + "Key inserted" + ); + assert_last_event( + crate::Event::Updated { + key: RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)), + old_value: None, + new_value: Some(RuntimeParametersValue::Pallet1(pallet1::ParametersValue::Key3(2))), + } + .into(), + ); + + // It will also emit a second event: + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(2))), + )); + assert_eq!(frame_system::Pallet::::events().len(), 2); + }); +} + +#[test] +fn set_parameters_wrong_origin_errors() { + new_test_ext().execute_with(|| { + // Pallet1 is root origin only: + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_noop!( + PalletParameters::set_parameter( + Origin::signed(1), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(432))), + ), + DispatchError::BadOrigin + ); + + // Pallet2 is signed origin only: + assert_ok!(PalletParameters::set_parameter( + Origin::signed(1), + Pallet2(pallet2::Parameters::Key3(pallet2::Key3, Some(123))), + )); + + assert_noop!( + PalletParameters::set_parameter( + Origin::root(), + Pallet2(pallet2::Parameters::Key3(pallet2::Key3, Some(432))), + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn get_through_external_pallet_works() { + new_test_ext().execute_with(|| { + assert_eq!(::MagicNumber::get(), 0); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key1(pallet1::Key1, Some(123))), + )); + + assert_eq!(::MagicNumber::get(), 123); + }); +} + +#[test] +fn test_define_parameters_key_convert() { + let key1 = pallet1::Key1; + let parameter_key: pallet1::ParametersKey = key1.clone().into(); + let key1_2: pallet1::Key1 = parameter_key.clone().try_into().unwrap(); + + assert_eq!(key1, key1_2); + assert_eq!(parameter_key, pallet1::ParametersKey::Key1(key1)); + + let key2 = pallet1::Key2; + let parameter_key: pallet1::ParametersKey = key2.clone().into(); + let key2_2: pallet1::Key2 = parameter_key.clone().try_into().unwrap(); + + assert_eq!(key2, key2_2); + assert_eq!(parameter_key, pallet1::ParametersKey::Key2(key2)); +} + +#[test] +fn test_define_parameters_value_convert() { + let value1 = pallet1::Key1Value(1); + let parameter_value: pallet1::ParametersValue = value1.clone().into(); + let value1_2: pallet1::Key1Value = parameter_value.clone().try_into().unwrap(); + + assert_eq!(value1, value1_2); + assert_eq!(parameter_value, pallet1::ParametersValue::Key1(1)); + + let value2 = pallet1::Key2Value(2); + let parameter_value: pallet1::ParametersValue = value2.clone().into(); + let value2_2: pallet1::Key2Value = parameter_value.clone().try_into().unwrap(); + + assert_eq!(value2, value2_2); + assert_eq!(parameter_value, pallet1::ParametersValue::Key2(2)); +} + +#[test] +fn test_define_parameters_aggregrated_key_value() { + let kv1 = pallet1::Parameters::Key1(pallet1::Key1, None); + let (key1, value1) = kv1.clone().into_parts(); + + assert_eq!(key1, pallet1::ParametersKey::Key1(pallet1::Key1)); + assert_eq!(value1, None); + + let kv2 = pallet1::Parameters::Key2(pallet1::Key2, Some(2)); + let (key2, value2) = kv2.clone().into_parts(); + + assert_eq!(key2, pallet1::ParametersKey::Key2(pallet1::Key2)); + assert_eq!(value2, Some(pallet1::ParametersValue::Key2(2))); +} + +#[test] +fn test_define_aggregrated_parameters_key_convert() { + use codec::Encode; + + let key1 = pallet1::Key1; + let parameter_key: pallet1::ParametersKey = key1.clone().into(); + let runtime_key: RuntimeParametersKey = parameter_key.clone().into(); + + assert_eq!(runtime_key, RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(key1))); + assert_eq!(runtime_key.encode(), vec![3, 0]); + + let key2 = pallet2::Key2; + let parameter_key: pallet2::ParametersKey = key2.clone().into(); + let runtime_key: RuntimeParametersKey = parameter_key.clone().into(); + + assert_eq!(runtime_key, RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(key2))); + assert_eq!(runtime_key.encode(), vec![1, 1]); +} + +#[test] +fn test_define_aggregrated_parameters_aggregrated_key_value() { + let kv1 = RuntimeParameters::Pallet1(pallet1::Parameters::Key1(pallet1::Key1, None)); + let (key1, value1) = kv1.clone().into_parts(); + + assert_eq!(key1, RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(pallet1::Key1))); + assert_eq!(value1, None); + + let kv2 = RuntimeParameters::Pallet2(pallet2::Parameters::Key2(pallet2::Key2, Some(2))); + let (key2, value2) = kv2.clone().into_parts(); + + assert_eq!(key2, RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(pallet2::Key2))); + assert_eq!(value2, Some(RuntimeParametersValue::Pallet2(pallet2::ParametersValue::Key2(2)))); +} + +#[test] +fn codec_index_works() { + let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(pallet1::Key1)).encode(); + assert_eq!(enc, vec![3, 0]); + let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key2(pallet1::Key2)).encode(); + assert_eq!(enc, vec![3, 1]); + let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)).encode(); + assert_eq!(enc, vec![3, 2]); + + let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key1(pallet2::Key1)).encode(); + assert_eq!(enc, vec![1, 2]); + let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(pallet2::Key2)).encode(); + assert_eq!(enc, vec![1, 1]); + let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key3(pallet2::Key3)).encode(); + assert_eq!(enc, vec![1, 0]); +} diff --git a/substrate/frame/parameters/src/weights.rs b/substrate/frame/parameters/src/weights.rs new file mode 100644 index 0000000000000000000000000000000000000000..6746960b1b711995b952f6b5784e71debc41a688 --- /dev/null +++ b/substrate/frame/parameters/src/weights.rs @@ -0,0 +1,76 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_parameters` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-02-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_parameters +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/parameters/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_parameters`. +pub trait WeightInfo { + fn set_parameter() -> Weight; +} + +/// Weights for `pallet_parameters` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn set_parameter() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + fn set_parameter() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + } +} diff --git a/substrate/frame/preimage/Cargo.toml b/substrate/frame/preimage/Cargo.toml index 5951663d291423bc5b41213531578d816bc0de4e..10a15f97bd5a72417243b86c81cc7b3ddf1db2a6 100644 --- a/substrate/frame/preimage/Cargo.toml +++ b/substrate/frame/preimage/Cargo.toml @@ -21,7 +21,7 @@ sp-core = { path = "../../primitives/core", default-features = false, optional = sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/substrate/frame/preimage/src/mock.rs b/substrate/frame/preimage/src/mock.rs index 60ffecbb448059a375b0778dbfd3d38a3f88c846..a43e8347d76bf219c2f2fe3a36e4505f1d48c4a0 100644 --- a/substrate/frame/preimage/src/mock.rs +++ b/substrate/frame/preimage/src/mock.rs @@ -22,13 +22,12 @@ use super::*; use crate as pallet_preimage; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{fungible::HoldConsideration, ConstU32, ConstU64, Everything}, - weights::constants::RocksDbWeight, + traits::{fungible::HoldConsideration, ConstU32, ConstU64}, }; use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, Convert, IdentityLookup}, + traits::{BlakeTwo256, Convert}, BuildStorage, }; @@ -45,29 +44,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/ranked-collective/Cargo.toml b/substrate/frame/ranked-collective/Cargo.toml index 62e01df372bdef15e9f2ee3e43ce4dc5a06044eb..54e84c0b55887d55017f8c16447424a594185f10 100644 --- a/substrate/frame/ranked-collective/Cargo.toml +++ b/substrate/frame/ranked-collective/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/ranked-collective/src/benchmarking.rs b/substrate/frame/ranked-collective/src/benchmarking.rs index 332d8292e53f1c8bd936a3fd19e9bd59f52d884b..462f55a238d2a867f900e4653369e5fa9368d874 100644 --- a/substrate/frame/ranked-collective/src/benchmarking.rs +++ b/substrate/frame/ranked-collective/src/benchmarking.rs @@ -41,8 +41,8 @@ fn make_member, I: 'static>(rank: Rank) -> T::AccountId { let who = account::("member", MemberCount::::get(0), SEED); let who_lookup = T::Lookup::unlookup(who.clone()); assert_ok!(Pallet::::add_member( - T::PromoteOrigin::try_successful_origin() - .expect("PromoteOrigin has no successful origin required for the benchmark"), + T::AddOrigin::try_successful_origin() + .expect("AddOrigin has no successful origin required for the benchmark"), who_lookup.clone(), )); for _ in 0..rank { @@ -60,7 +60,7 @@ benchmarks_instance_pallet! { let who = account::("member", 0, SEED); let who_lookup = T::Lookup::unlookup(who.clone()); let origin = - T::PromoteOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + T::AddOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::add_member { who: who_lookup }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -77,7 +77,7 @@ benchmarks_instance_pallet! { let last = make_member::(rank); let last_index = (0..=rank).map(|r| IdToIndex::::get(r, &last).unwrap()).collect::>(); let origin = - T::DemoteOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + T::RemoveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::remove_member { who: who_lookup, min_rank: rank }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -125,23 +125,11 @@ benchmarks_instance_pallet! { } vote { - let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert_ok!(Pallet::::add_member( - T::PromoteOrigin::try_successful_origin() - .expect("PromoteOrigin has no successful origin required for the benchmark"), - caller_lookup.clone(), - )); - // Create a poll let class = T::Polls::classes().into_iter().next().unwrap(); let rank = T::MinRankOfClass::convert(class.clone()); - for _ in 0..rank { - assert_ok!(Pallet::::promote_member( - T::PromoteOrigin::try_successful_origin() - .expect("PromoteOrigin has no successful origin required for the benchmark"), - caller_lookup.clone(), - )); - } + + let caller = make_member::(rank); + let caller_lookup = T::Lookup::unlookup(caller.clone()); let poll = T::Polls::create_ongoing(class).expect("Must always be able to create a poll for rank 0"); @@ -193,5 +181,5 @@ benchmarks_instance_pallet! { assert_has_event::(Event::MemberExchanged { who, new_who }.into()); } - impl_benchmark_test_suite!(RankedCollective, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(RankedCollective, crate::tests::ExtBuilder::default().build(), crate::tests::Test); } diff --git a/substrate/frame/ranked-collective/src/lib.rs b/substrate/frame/ranked-collective/src/lib.rs index d0303fee0b3670adcb723ba652ec8b8b50919afa..ceaf03de211008ee49bf4a4a1fc961bf8825971a 100644 --- a/substrate/frame/ranked-collective/src/lib.rs +++ b/substrate/frame/ranked-collective/src/lib.rs @@ -39,7 +39,6 @@ //! least a particular rank. #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "128"] use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -393,12 +392,20 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// The origin required to add or promote a mmember. The success value indicates the + /// The origin required to add a member. + type AddOrigin: EnsureOrigin; + + /// The origin required to remove a member. + /// + /// The success value indicates the maximum rank *from which* the removal may be. + type RemoveOrigin: EnsureOrigin; + + /// The origin required to promote a member. The success value indicates the /// maximum rank *to which* the promotion may be. type PromoteOrigin: EnsureOrigin; - /// The origin required to demote or remove a member. The success value indicates the - /// maximum rank *from which* the demotion/removal may be. + /// The origin required to demote a member. The success value indicates the + /// maximum rank *from which* the demotion may be. type DemoteOrigin: EnsureOrigin; /// The origin that can swap the account of a member. @@ -510,22 +517,21 @@ pub mod pallet { impl, I: 'static> Pallet { /// Introduce a new member. /// - /// - `origin`: Must be the `AdminOrigin`. + /// - `origin`: Must be the `AddOrigin`. /// - `who`: Account of non-member which will become a member. - /// - `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)?; + T::AddOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; Self::do_add_member(who, true) } /// Increment the rank of an existing member by one. /// - /// - `origin`: Must be the `AdminOrigin`. + /// - `origin`: Must be the `PromoteOrigin`. /// - `who`: Account of existing member. /// /// Weight: `O(1)` @@ -540,7 +546,7 @@ pub mod pallet { /// Decrement the rank of an existing member by one. If the member is already at rank zero, /// then they are removed entirely. /// - /// - `origin`: Must be the `AdminOrigin`. + /// - `origin`: Must be the `DemoteOrigin`. /// - `who`: Account of existing member of rank greater than zero. /// /// Weight: `O(1)`, less if the member's index is highest in its rank. @@ -554,7 +560,7 @@ pub mod pallet { /// Remove the member entirely. /// - /// - `origin`: Must be the `AdminOrigin`. + /// - `origin`: Must be the `RemoveOrigin`. /// - `who`: Account of existing member of rank greater than zero. /// - `min_rank`: The rank of the member or greater. /// @@ -566,7 +572,7 @@ pub mod pallet { who: AccountIdLookupOf, min_rank: Rank, ) -> DispatchResultWithPostInfo { - let max_rank = T::DemoteOrigin::ensure_origin(origin)?; + let max_rank = T::RemoveOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; let MemberRecord { rank, .. } = Self::ensure_member(&who)?; ensure!(min_rank >= rank, Error::::InvalidWitness); @@ -709,6 +715,14 @@ pub mod pallet { } } + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state() + } + } + impl, I: 'static> Pallet { fn ensure_member(who: &T::AccountId) -> Result { Members::::get(who).ok_or(Error::::NotMember.into()) @@ -840,6 +854,132 @@ pub mod pallet { } } + #[cfg(any(feature = "try-runtime", test))] + impl, I: 'static> Pallet { + /// Ensure the correctness of the state of this pallet. + pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + Self::try_state_members()?; + Self::try_state_index()?; + + Ok(()) + } + + /// ### Invariants of Member storage items + /// + /// Total number of [`Members`] in storage should be >= [`MemberIndex`] of a [`Rank`] in + /// [`MemberCount`]. + /// [`Rank`] in Members should be in [`MemberCount`] + /// [`Sum`] of [`MemberCount`] index should be the same as the sum of all the index attained + /// for rank possessed by [`Members`] + fn try_state_members() -> Result<(), sp_runtime::TryRuntimeError> { + MemberCount::::iter().try_for_each(|(_, member_index)| -> DispatchResult { + let total_members = Members::::iter().count(); + ensure!( + total_members as u32 >= member_index, + "Total count of `Members` should be greater than or equal to the number of `MemberIndex` of a particular `Rank` in `MemberCount`." + ); + + Ok(()) + })?; + + let mut sum_of_member_rank_indexes = 0; + Members::::iter().try_for_each(|(_, member_record)| -> DispatchResult { + ensure!( + Self::is_rank_in_member_count(member_record.rank.into()), + "`Rank` in Members should be in `MemberCount`" + ); + + sum_of_member_rank_indexes += Self::determine_index_of_a_rank(member_record.rank); + + Ok(()) + })?; + + let sum_of_all_member_count_indexes = + MemberCount::::iter_values().fold(0, |sum, index| sum + index); + ensure!( + sum_of_all_member_count_indexes == sum_of_member_rank_indexes as u32, + "Sum of `MemberCount` index should be the same as the sum of all the index attained for rank possessed by `Members`" + ); + Ok(()) + } + + /// ### Invariants of Index storage items + /// [`Member`] in storage of [`IdToIndex`] should be the same as [`Member`] in [`IndexToId`] + /// [`Rank`] in [`IdToIndex`] should be the same as the the [`Rank`] in [`IndexToId`] + /// [`Rank`] of the member [`who`] in [`IdToIndex`] should be the same as the [`Rank`] of + /// the member [`who`] in [`Members`] + fn try_state_index() -> Result<(), sp_runtime::TryRuntimeError> { + IdToIndex::::iter().try_for_each( + |(rank, who, member_index)| -> DispatchResult { + let who_from_index = IndexToId::::get(rank, member_index).unwrap(); + ensure!( + who == who_from_index, + "`Member` in storage of `IdToIndex` should be the same as `Member` in `IndexToId`." + ); + + ensure!( + Self::is_rank_in_index_to_id_storage(rank.into()), + "`Rank` in `IdToIndex` should be the same as the `Rank` in `IndexToId`" + ); + Ok(()) + }, + )?; + + Members::::iter().try_for_each(|(who, member_record)| -> DispatchResult { + ensure!( + Self::is_who_rank_in_id_to_index_storage(who, member_record.rank), + "`Rank` of the member `who` in `IdToIndex` should be the same as the `Rank` of the member `who` in `Members`" + ); + + Ok(()) + })?; + + Ok(()) + } + + /// Checks if a rank is part of the `MemberCount` + fn is_rank_in_member_count(rank: u32) -> bool { + for (r, _) in MemberCount::::iter() { + if r as u32 == rank { + return true; + } + } + + return false; + } + + /// Checks if a rank is the same as the rank `IndexToId` + fn is_rank_in_index_to_id_storage(rank: u32) -> bool { + for (r, _, _) in IndexToId::::iter() { + if r as u32 == rank { + return true; + } + } + + return false; + } + + /// Checks if a member(who) rank is the same as the rank of a member(who) in `IdToIndex` + fn is_who_rank_in_id_to_index_storage(who: T::AccountId, rank: u16) -> bool { + for (rank_, who_, _) in IdToIndex::::iter() { + if who == who_ && rank == rank_ { + return true; + } + } + + return false; + } + + /// Determines the total index for a rank + fn determine_index_of_a_rank(rank: u16) -> u16 { + let mut sum = 0; + for _ in 0..rank + 1 { + sum += 1; + } + sum + } + } + impl, I: 'static> RankedMembers for Pallet { type AccountId = T::AccountId; type Rank = Rank; diff --git a/substrate/frame/ranked-collective/src/tests.rs b/substrate/frame/ranked-collective/src/tests.rs index 58e21ded358147fe7b7546be2df9cced94fde089..31add52d90afa9c8756be88b425a08b453b60892 100644 --- a/substrate/frame/ranked-collective/src/tests.rs +++ b/substrate/frame/ranked-collective/src/tests.rs @@ -26,7 +26,10 @@ use frame_support::{ traits::{ConstU16, EitherOf, MapSuccess, Polling}, }; use sp_core::Get; -use sp_runtime::{traits::ReduceBy, BuildStorage}; +use sp_runtime::{ + traits::{ReduceBy, ReplaceWithDefault}, + BuildStorage, +}; use super::*; use crate as pallet_ranked_collective; @@ -152,6 +155,8 @@ parameter_types! { impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; + type AddOrigin = MapSuccess>; + type RemoveOrigin = Self::DemoteOrigin; type PromoteOrigin = EitherOf< // Root can promote arbitrarily. frame_system::EnsureRootWithSuccess>, @@ -178,11 +183,28 @@ impl Config for Test { type BenchmarkSetup = (); } -pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext +pub struct ExtBuilder {} + +impl Default for ExtBuilder { + fn default() -> Self { + Self {} + } +} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + Club::do_try_state().expect("All invariants must hold after a test"); + }) + } } fn next_block() { @@ -220,14 +242,14 @@ fn completed_poll_should_panic() { #[test] fn basic_stuff() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); }); } #[test] fn member_lifecycle_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::demote_member(RuntimeOrigin::root(), 1)); @@ -239,7 +261,7 @@ fn member_lifecycle_works() { #[test] fn add_remove_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_eq!(member_count(0), 1); @@ -269,7 +291,7 @@ fn add_remove_works() { #[test] fn promote_demote_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_noop!(Club::add_member(RuntimeOrigin::signed(1), 1), DispatchError::BadOrigin); assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_eq!(member_count(0), 1); @@ -300,7 +322,7 @@ fn promote_demote_works() { #[test] fn promote_demote_by_rank_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); for _ in 0..7 { assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); @@ -367,7 +389,7 @@ fn promote_demote_by_rank_works() { #[test] fn voting_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 0)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); @@ -401,7 +423,7 @@ fn voting_works() { #[test] fn cleanup_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); @@ -428,7 +450,7 @@ fn cleanup_works() { #[test] fn remove_member_cleanup_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); @@ -454,7 +476,7 @@ fn remove_member_cleanup_works() { #[test] fn ensure_ranked_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); @@ -523,7 +545,7 @@ fn ensure_ranked_works() { #[test] fn do_add_member_to_rank_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let max_rank = 9u16; assert_ok!(Club::do_add_member_to_rank(69, max_rank / 2, true)); assert_ok!(Club::do_add_member_to_rank(1337, max_rank, true)); @@ -540,7 +562,7 @@ fn do_add_member_to_rank_works() { #[test] fn tally_support_correct() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { // add members, // rank 1: accounts 1, 2, 3 // rank 2: accounts 2, 3 @@ -580,7 +602,7 @@ fn tally_support_correct() { #[test] fn exchange_member_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_eq!(member_count(0), 1); @@ -608,7 +630,7 @@ fn exchange_member_works() { #[test] fn exchange_member_same_noops() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index ba7958f7dd460af9c6d7f3831c73d41f363268a2..89374527e069c4eac7eac1173502768d1cef684d 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -22,13 +22,9 @@ use super::*; use crate as recovery; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, OnFinalize, OnInitialize}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + traits::{OnFinalize, OnInitialize}, }; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -43,29 +39,8 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { diff --git a/substrate/frame/referenda/Cargo.toml b/substrate/frame/referenda/Cargo.toml index 0f8a92ff72a6ebb7094e6dca9634f853346f2e6c..2dfb25a2fd3a5b50bdc7828e50c8e247cd7f936a 100644 --- a/substrate/frame/referenda/Cargo.toml +++ b/substrate/frame/referenda/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } sp-arithmetic = { path = "../../primitives/arithmetic", default-features = false } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } @@ -29,7 +29,7 @@ frame-system = { path = "../system", default-features = false } sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] assert_matches = { version = "1.5" } diff --git a/substrate/frame/referenda/src/lib.rs b/substrate/frame/referenda/src/lib.rs index 8912f9ad217371846fc193f495ff1e49b0d18f00..c5bf2266e672542b77c79bee71176f8e526018d4 100644 --- a/substrate/frame/referenda/src/lib.rs +++ b/substrate/frame/referenda/src/lib.rs @@ -433,6 +433,11 @@ pub mod pallet { Self::do_try_state()?; Ok(()) } + + #[cfg(any(feature = "std", test))] + fn integrity_test() { + T::Tracks::check_integrity().expect("Static tracks configuration is valid."); + } } #[pallet::call] diff --git a/substrate/frame/referenda/src/mock.rs b/substrate/frame/referenda/src/mock.rs index d9f9042fefc9fd9104feea2637224ad496b04590..bfafc107c28bc4ba422d6409b861f60a6c1d26d9 100644 --- a/substrate/frame/referenda/src/mock.rs +++ b/substrate/frame/referenda/src/mock.rs @@ -29,9 +29,8 @@ use frame_support::{ weights::Weight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; -use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, Hash, IdentityLookup}, + traits::{BlakeTwo256, Hash}, BuildStorage, DispatchResult, Perbill, }; @@ -62,28 +61,8 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_preimage::Config for Test { type RuntimeEvent = RuntimeEvent; diff --git a/substrate/frame/referenda/src/types.rs b/substrate/frame/referenda/src/types.rs index 8d6a13ef27c981b3996425b18062e2ef04a2f68b..b3c583322cce384bae7e2a9340071d5af6b2f455 100644 --- a/substrate/frame/referenda/src/types.rs +++ b/substrate/frame/referenda/src/types.rs @@ -19,6 +19,7 @@ use super::*; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use core::fmt::Debug; use frame_support::{ traits::{schedule::v3::Anon, Bounded}, Parameter, @@ -26,7 +27,6 @@ use frame_support::{ use scale_info::TypeInfo; use sp_arithmetic::{Rounding::*, SignedRounding::*}; use sp_runtime::{FixedI64, PerThing, RuntimeDebug}; -use sp_std::fmt::Debug; pub type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; @@ -144,7 +144,10 @@ pub trait TracksInfo { /// The origin type from which a track is implied. type RuntimeOrigin; - /// Return the array of known tracks and their information. + /// Sorted array of known tracks and their information. + /// + /// The array MUST be sorted by `Id`. Consumers of this trait are advised to assert + /// [`Self::check_integrity`] prior to any use. fn tracks() -> &'static [(Self::Id, TrackInfo)]; /// Determine the voting track for the given `origin`. @@ -152,7 +155,23 @@ pub trait TracksInfo { /// Return the track info for track `id`, by default this just looks it up in `Self::tracks()`. fn info(id: Self::Id) -> Option<&'static TrackInfo> { - Self::tracks().iter().find(|x| x.0 == id).map(|x| &x.1) + let tracks = Self::tracks(); + let maybe_index = tracks.binary_search_by_key(&id, |t| t.0).ok()?; + + tracks.get(maybe_index).map(|(_, info)| info) + } + + /// Check assumptions about the static data that this trait provides. + fn check_integrity() -> Result<(), &'static str> + where + Balance: 'static, + Moment: 'static, + { + if Self::tracks().windows(2).all(|w| w[0].0 < w[1].0) { + Ok(()) + } else { + Err("The tracks that were returned by `tracks` were not sorted by `Id`") + } } } @@ -670,4 +689,72 @@ mod tests { assert_eq!(c.delay(pc(30).less_epsilon()), pc(100)); assert_eq!(c.delay(pc(0)), pc(100)); } + + #[test] + fn tracks_integrity_check_detects_unsorted() { + use crate::mock::RuntimeOrigin; + + pub struct BadTracksInfo; + impl TracksInfo for BadTracksInfo { + type Id = u8; + type RuntimeOrigin = ::PalletsOrigin; + fn tracks() -> &'static [(Self::Id, TrackInfo)] { + static DATA: [(u8, TrackInfo); 2] = [ + ( + 1u8, + TrackInfo { + name: "root", + max_deciding: 1, + decision_deposit: 10, + prepare_period: 4, + decision_period: 4, + confirm_period: 2, + min_enactment_period: 4, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), + }, + }, + ), + ( + 0u8, + TrackInfo { + name: "none", + max_deciding: 3, + decision_deposit: 1, + prepare_period: 2, + decision_period: 2, + confirm_period: 1, + min_enactment_period: 2, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(95), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(90), + ceil: Perbill::from_percent(100), + }, + }, + ), + ]; + &DATA[..] + } + fn track_for(_: &Self::RuntimeOrigin) -> Result { + unimplemented!() + } + } + + assert_eq!( + BadTracksInfo::check_integrity(), + Err("The tracks that were returned by `tracks` were not sorted by `Id`") + ); + } } diff --git a/substrate/frame/remark/Cargo.toml b/substrate/frame/remark/Cargo.toml index 582f5958a6925c601d5b1e90c765131fc8605eca..45710f0539e255b48caf4e7dce9a99f46600d4e5 100644 --- a/substrate/frame/remark/Cargo.toml +++ b/substrate/frame/remark/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/remark/src/mock.rs b/substrate/frame/remark/src/mock.rs index 5a5e6a3ccdff28ff29799ed7c31cf2c1c73c77bb..d89583513b60d16a79aa076fb963fdc0e1459b76 100644 --- a/substrate/frame/remark/src/mock.rs +++ b/substrate/frame/remark/src/mock.rs @@ -18,15 +18,8 @@ //! Test environment for remarks pallet. use crate as pallet_remark; -use frame_support::{ - derive_impl, - traits::{ConstU16, ConstU32, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use frame_support::derive_impl; +use sp_runtime::BuildStorage; pub type Block = frame_system::mocking::MockBlock; @@ -41,29 +34,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = ConstU16<42>; - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_remark::Config for Test { diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index e9e9423e2ac261e451f51c4bbbf4c332982ff080..c194b757692327e5aa7c8f3b807f6a94134077ac 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -27,13 +27,7 @@ use frame_support::{ traits::{ConstU32, ConstU64, Hooks, OneSessionHandler}, }; use pallet_staking::StakerStatus; -use sp_core::H256; -use sp_runtime::{ - curve::PiecewiseLinear, - testing::UintAuthorityId, - traits::{BlakeTwo256, IdentityLookup, Zero}, - BuildStorage, -}; +use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{EraIndex, SessionIndex}; use sp_std::collections::btree_map::BTreeMap; @@ -86,29 +80,8 @@ impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/safe-mode/src/lib.rs b/substrate/frame/safe-mode/src/lib.rs index 325d751e17f61e6af9094fa3fc515c95a0edfdb8..2bf2ebee0a4ac88aebe6d2ccc498ac8b5d958352 100644 --- a/substrate/frame/safe-mode/src/lib.rs +++ b/substrate/frame/safe-mode/src/lib.rs @@ -79,6 +79,7 @@ pub mod mock; mod tests; pub mod weights; +use core::convert::TryInto; use frame_support::{ defensive_assert, pallet_prelude::*, @@ -96,7 +97,6 @@ use frame_support::{ use frame_system::pallet_prelude::*; use sp_arithmetic::traits::Zero; use sp_runtime::traits::Saturating; -use sp_std::{convert::TryInto, prelude::*}; pub use pallet::*; pub use weights::*; diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml index 90a44da1a10459410240506b22c944efab35a6e7..ba57fd46eebb44f0164ffd8aa2a5edf936ab3835 100644 --- a/substrate/frame/salary/Cargo.toml +++ b/substrate/frame/salary/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/salary/src/lib.rs b/substrate/frame/salary/src/lib.rs index 18e5c624218c58e7c696129be8541934bb6ef851..efb4f5d3c5422d96b2b2038122de8c2d391cc855 100644 --- a/substrate/frame/salary/src/lib.rs +++ b/substrate/frame/salary/src/lib.rs @@ -18,13 +18,12 @@ //! Make periodic payment to members of a ranked collective according to rank. #![cfg_attr(not(feature = "std"), no_std)] -#![recursion_limit = "128"] use codec::{Decode, Encode, MaxEncodedLen}; +use core::marker::PhantomData; use scale_info::TypeInfo; use sp_arithmetic::traits::{Saturating, Zero}; use sp_runtime::{Perbill, RuntimeDebug}; -use sp_std::{marker::PhantomData, prelude::*}; use frame_support::{ defensive, diff --git a/substrate/frame/salary/src/tests/integration.rs b/substrate/frame/salary/src/tests/integration.rs index bf1e82b028c6da58e5705fae62b30b95a40e215c..a49b5637b8ae42b6618fe98b12ceba77f35fe89b 100644 --- a/substrate/frame/salary/src/tests/integration.rs +++ b/substrate/frame/salary/src/tests/integration.rs @@ -26,7 +26,7 @@ use frame_support::{ use pallet_ranked_collective::{EnsureRanked, Geometric, TallyOf, Votes}; use sp_core::{ConstU16, Get}; use sp_runtime::{ - traits::{Convert, ReduceBy}, + traits::{Convert, ReduceBy, ReplaceWithDefault}, BuildStorage, DispatchError, }; @@ -162,12 +162,14 @@ impl pallet_ranked_collective::Config for Test { // Members can promote up to the rank of 2 below them. MapSuccess, ReduceBy>>, >; + type AddOrigin = MapSuccess>; type DemoteOrigin = EitherOf< // Root can demote arbitrarily. frame_system::EnsureRootWithSuccess>, // Members can demote up to the rank of 3 below them. MapSuccess, ReduceBy>>, >; + type RemoveOrigin = Self::DemoteOrigin; type ExchangeOrigin = EitherOf< // Root can exchange arbitrarily. frame_system::EnsureRootWithSuccess>, diff --git a/substrate/frame/salary/src/tests/unit.rs b/substrate/frame/salary/src/tests/unit.rs index 07ef5ce63d5b981ecf523632ef7754b9c599f82f..b3fd00ec76b9b018a8949e5dc6a36385d99ad8d5 100644 --- a/substrate/frame/salary/src/tests/unit.rs +++ b/substrate/frame/salary/src/tests/unit.rs @@ -19,6 +19,7 @@ use std::collections::BTreeMap; +use core::cell::RefCell; use frame_support::{ assert_noop, assert_ok, derive_impl, pallet_prelude::Weight, @@ -26,7 +27,6 @@ use frame_support::{ traits::{tokens::ConvertRank, ConstU64}, }; use sp_runtime::{traits::Identity, BuildStorage, DispatchResult}; -use sp_std::cell::RefCell; use crate as pallet_salary; use crate::*; diff --git a/substrate/frame/sassafras/Cargo.toml b/substrate/frame/sassafras/Cargo.toml index 5f4900b5a29d4514ab8621e8689aba1a63bcb640..325a39bf59799f6b314d54ad131b892eec61ec44 100644 --- a/substrate/frame/sassafras/Cargo.toml +++ b/substrate/frame/sassafras/Cargo.toml @@ -22,7 +22,7 @@ scale-info = { version = "2.5.0", default-features = false, features = ["derive" frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } sp-consensus-sassafras = { path = "../../primitives/consensus/sassafras", default-features = false, features = ["serde"] } sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } diff --git a/substrate/frame/sassafras/src/mock.rs b/substrate/frame/sassafras/src/mock.rs index 82ea2fe1ef29e6cc47e2f5e972314ecef6d909a3..5e5909fcb0d6e0d4db41ce96035fa9322cf65bbd 100644 --- a/substrate/frame/sassafras/src/mock.rs +++ b/substrate/frame/sassafras/src/mock.rs @@ -99,7 +99,7 @@ pub fn new_test_ext_with_pairs( pallet_sassafras::GenesisConfig:: { authorities: authorities.clone(), epoch_config: TEST_EPOCH_CONFIGURATION, - _phantom: sp_std::marker::PhantomData, + _phantom: core::marker::PhantomData, } .assimilate_storage(&mut storage) .unwrap(); diff --git a/substrate/frame/scheduler/Cargo.toml b/substrate/frame/scheduler/Cargo.toml index bca17242d2071e22c232baab294d8dba0a27d77c..f50f6afdc06379f0ffaca3460df314e1eb0e4945 100644 --- a/substrate/frame/scheduler/Cargo.toml +++ b/substrate/frame/scheduler/Cargo.toml @@ -14,7 +14,7 @@ workspace = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/scheduler/src/benchmarking.rs b/substrate/frame/scheduler/src/benchmarking.rs index cc86a17973780870970a7b604b4fd5d30cffe263..18441d54b39a2244f1eb9677d38be2576e141d94 100644 --- a/substrate/frame/scheduler/src/benchmarking.rs +++ b/substrate/frame/scheduler/src/benchmarking.rs @@ -22,12 +22,13 @@ use frame_benchmarking::v1::{account, benchmarks, BenchmarkError}; use frame_support::{ ensure, traits::{schedule::Priority, BoundedInline}, + weights::WeightMeter, }; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use sp_std::{prelude::*, vec}; use crate::Pallet as Scheduler; -use frame_system::Call as SystemCall; +use frame_system::{Call as SystemCall, EventRecord}; const SEED: u32 = 0; @@ -35,6 +36,14 @@ const BLOCK_NUMBER: u32 = 2; type SystemOrigin = ::RuntimeOrigin; +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + /// Add `n` items to the schedule. /// /// For `resolved`: @@ -306,5 +315,105 @@ benchmarks! { ); } + schedule_retry { + let s in 1 .. T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let period: BlockNumberFor = 1u32.into(); + let root: ::PalletsOrigin = frame_system::RawOrigin::Root.into(); + let retry_config = RetryConfig { total_retries: 10, remaining: 10, period }; + Retries::::insert(address, retry_config); + let (mut when, index) = address; + let task = Agenda::::get(when)[index as usize].clone().unwrap(); + let mut weight_counter = WeightMeter::with_limit(T::MaximumWeight::get()); + }: { + Scheduler::::schedule_retry(&mut weight_counter, when, when, index, &task, retry_config); + } verify { + when = when + BlockNumberFor::::one(); + assert_eq!( + Retries::::get((when, 0)), + Some(RetryConfig { total_retries: 10, remaining: 9, period }) + ); + } + + set_retry { + let s = T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let (when, index) = address; + let period = BlockNumberFor::::one(); + }: _(RawOrigin::Root, (when, index), 10, period) + verify { + assert_eq!( + Retries::::get((when, index)), + Some(RetryConfig { total_retries: 10, remaining: 10, period }) + ); + assert_last_event::( + Event::RetrySet { task: address, id: None, period, retries: 10 }.into(), + ); + } + + set_retry_named { + let s = T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let (when, index) = address; + let period = BlockNumberFor::::one(); + }: _(RawOrigin::Root, name, 10, period) + verify { + assert_eq!( + Retries::::get((when, index)), + Some(RetryConfig { total_retries: 10, remaining: 10, period }) + ); + assert_last_event::( + Event::RetrySet { task: address, id: Some(name), period, retries: 10 }.into(), + ); + } + + cancel_retry { + let s = T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let (when, index) = address; + let period = BlockNumberFor::::one(); + assert!(Scheduler::::set_retry(RawOrigin::Root.into(), (when, index), 10, period).is_ok()); + }: _(RawOrigin::Root, (when, index)) + verify { + assert!(!Retries::::contains_key((when, index))); + assert_last_event::( + Event::RetryCancelled { task: address, id: None }.into(), + ); + } + + cancel_retry_named { + let s = T::MaxScheduledPerBlock::get(); + let when = BLOCK_NUMBER.into(); + + fill_schedule::(when, s)?; + let name = u32_to_name(s - 1); + let address = Lookup::::get(name).unwrap(); + let (when, index) = address; + let period = BlockNumberFor::::one(); + assert!(Scheduler::::set_retry_named(RawOrigin::Root.into(), name, 10, period).is_ok()); + }: _(RawOrigin::Root, name) + verify { + assert!(!Retries::::contains_key((when, index))); + assert_last_event::( + Event::RetryCancelled { task: address, id: Some(name) }.into(), + ); + } + impl_benchmark_test_suite!(Scheduler, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/substrate/frame/scheduler/src/lib.rs b/substrate/frame/scheduler/src/lib.rs index e94f154eee32f908dc449ba7ff9e54853cc4b915..daebebdee9956a2317ca6d755999797a52ac42ce 100644 --- a/substrate/frame/scheduler/src/lib.rs +++ b/substrate/frame/scheduler/src/lib.rs @@ -122,6 +122,17 @@ pub type CallOrHashOf = pub type BoundedCallOf = Bounded<::RuntimeCall, ::Hashing>; +/// The configuration of the retry mechanism for a given task along with its current state. +#[derive(Clone, Copy, RuntimeDebug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct RetryConfig { + /// Initial amount of retries allowed. + total_retries: u8, + /// Amount of retries left. + remaining: u8, + /// Period of time between retry attempts. + period: Period, +} + #[cfg_attr(any(feature = "std", test), derive(PartialEq, Eq))] #[derive(Clone, RuntimeDebug, Encode, Decode)] struct ScheduledV1 { @@ -148,6 +159,26 @@ pub struct Scheduled { _phantom: PhantomData, } +impl + Scheduled +where + Call: Clone, + PalletsOrigin: Clone, +{ + /// Create a new task to be used for retry attempts of the original one. The cloned task will + /// have the same `priority`, `call` and `origin`, but will always be non-periodic and unnamed. + pub fn as_retry(&self) -> Self { + Self { + maybe_id: None, + priority: self.priority, + call: self.call.clone(), + maybe_periodic: None, + origin: self.origin.clone(), + _phantom: Default::default(), + } + } +} + use crate::{Scheduled as ScheduledV3, Scheduled as ScheduledV2}; pub type ScheduledV2Of = ScheduledV2< @@ -273,6 +304,16 @@ pub mod pallet { ValueQuery, >; + /// Retry configurations for items to be executed, indexed by task address. + #[pallet::storage] + pub type Retries = StorageMap< + _, + Blake2_128Concat, + TaskAddress>, + RetryConfig>, + OptionQuery, + >; + /// Lookup from a name to the block number and index of the task. /// /// For v3 -> v4 the previously unbounded identities are Blake2-256 hashed to form the v4 @@ -295,10 +336,22 @@ pub mod pallet { id: Option, result: DispatchResult, }, + /// Set a retry configuration for some task. + RetrySet { + task: TaskAddress>, + id: Option, + period: BlockNumberFor, + retries: u8, + }, + /// Cancel a retry configuration for some task. + RetryCancelled { task: TaskAddress>, id: Option }, /// The call for the provided hash was not found so the task has been aborted. 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 }, + /// The given task was unable to be retried since the agenda is full at that block or there + /// was not enough weight to reschedule it. + RetryFailed { task: TaskAddress>, id: Option }, /// The given task can never be executed since it is overweight. PermanentlyOverweight { task: TaskAddress>, id: Option }, } @@ -440,6 +493,111 @@ pub mod pallet { )?; Ok(()) } + + /// Set a retry configuration for a task so that, in case its scheduled run fails, it will + /// be retried after `period` blocks, for a total amount of `retries` retries or until it + /// succeeds. + /// + /// Tasks which need to be scheduled for a retry are still subject to weight metering and + /// agenda space, same as a regular task. If a periodic task fails, it will be scheduled + /// normally while the task is retrying. + /// + /// Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic + /// clones of the original task. Their retry configuration will be derived from the + /// original task's configuration, but will have a lower value for `remaining` than the + /// original `total_retries`. + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::set_retry())] + pub fn set_retry( + origin: OriginFor, + task: TaskAddress>, + retries: u8, + period: BlockNumberFor, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + let (when, index) = task; + let agenda = Agenda::::get(when); + let scheduled = agenda + .get(index as usize) + .and_then(Option::as_ref) + .ok_or(Error::::NotFound)?; + Self::ensure_privilege(origin.caller(), &scheduled.origin)?; + Retries::::insert( + (when, index), + RetryConfig { total_retries: retries, remaining: retries, period }, + ); + Self::deposit_event(Event::RetrySet { task, id: None, period, retries }); + Ok(()) + } + + /// Set a retry configuration for a named task so that, in case its scheduled run fails, it + /// will be retried after `period` blocks, for a total amount of `retries` retries or until + /// it succeeds. + /// + /// Tasks which need to be scheduled for a retry are still subject to weight metering and + /// agenda space, same as a regular task. If a periodic task fails, it will be scheduled + /// normally while the task is retrying. + /// + /// Tasks scheduled as a result of a retry for a periodic task are unnamed, non-periodic + /// clones of the original task. Their retry configuration will be derived from the + /// original task's configuration, but will have a lower value for `remaining` than the + /// original `total_retries`. + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::set_retry_named())] + pub fn set_retry_named( + origin: OriginFor, + id: TaskName, + retries: u8, + period: BlockNumberFor, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + let (when, agenda_index) = Lookup::::get(&id).ok_or(Error::::NotFound)?; + let agenda = Agenda::::get(when); + let scheduled = agenda + .get(agenda_index as usize) + .and_then(Option::as_ref) + .ok_or(Error::::NotFound)?; + Self::ensure_privilege(origin.caller(), &scheduled.origin)?; + Retries::::insert( + (when, agenda_index), + RetryConfig { total_retries: retries, remaining: retries, period }, + ); + Self::deposit_event(Event::RetrySet { + task: (when, agenda_index), + id: Some(id), + period, + retries, + }); + Ok(()) + } + + /// Removes the retry configuration of a task. + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::cancel_retry())] + pub fn cancel_retry( + origin: OriginFor, + task: TaskAddress>, + ) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + Self::do_cancel_retry(origin.caller(), task)?; + Self::deposit_event(Event::RetryCancelled { task, id: None }); + Ok(()) + } + + /// Cancel the retry configuration of a named task. + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::cancel_retry_named())] + pub fn cancel_retry_named(origin: OriginFor, id: TaskName) -> DispatchResult { + T::ScheduleOrigin::ensure_origin(origin.clone())?; + let origin = ::RuntimeOrigin::from(origin); + let task = Lookup::::get(&id).ok_or(Error::::NotFound)?; + Self::do_cancel_retry(origin.caller(), task)?; + Self::deposit_event(Event::RetryCancelled { task, id: Some(id) }); + Ok(()) + } } } @@ -838,12 +996,7 @@ impl Pallet { Ok(None), |s| -> Result>, DispatchError> { if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if matches!( - T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), - Some(Ordering::Less) | None - ) { - return Err(BadOrigin.into()) - } + Self::ensure_privilege(o, &s.origin)?; }; Ok(s.take()) }, @@ -854,6 +1007,7 @@ impl Pallet { if let Some(id) = s.maybe_id { Lookup::::remove(id); } + Retries::::remove((when, index)); Self::cleanup_agenda(when); Self::deposit_event(Event::Canceled { when, index }); Ok(()) @@ -931,12 +1085,8 @@ impl Pallet { Agenda::::try_mutate(when, |agenda| -> DispatchResult { if let Some(s) = agenda.get_mut(i) { if let (Some(ref o), Some(ref s)) = (origin, s.borrow()) { - if matches!( - T::OriginPrivilegeCmp::cmp_privilege(o, &s.origin), - Some(Ordering::Less) | None - ) { - return Err(BadOrigin.into()) - } + Self::ensure_privilege(o, &s.origin)?; + Retries::::remove((when, index)); T::Preimages::drop(&s.call); } *s = None; @@ -973,6 +1123,20 @@ impl Pallet { Self::deposit_event(Event::Canceled { when, index }); Self::place_task(new_time, task).map_err(|x| x.0) } + + fn do_cancel_retry( + origin: &T::PalletsOrigin, + (when, index): TaskAddress>, + ) -> Result<(), DispatchError> { + let agenda = Agenda::::get(when); + let scheduled = agenda + .get(index as usize) + .and_then(Option::as_ref) + .ok_or(Error::::NotFound)?; + Self::ensure_privilege(origin, &scheduled.origin)?; + Retries::::remove((when, index)); + Ok(()) + } } enum ServiceTaskError { @@ -1124,11 +1288,21 @@ impl Pallet { }, Err(()) => Err((Overweight, Some(task))), Ok(result) => { + let failed = result.is_err(); + let maybe_retry_config = Retries::::take((when, agenda_index)); Self::deposit_event(Event::Dispatched { task: (when, agenda_index), id: task.maybe_id, result, }); + + match maybe_retry_config { + Some(retry_config) if failed => { + Self::schedule_retry(weight, now, when, agenda_index, &task, retry_config); + }, + _ => {}, + } + if let &Some((period, count)) = &task.maybe_periodic { if count > 1 { task.maybe_periodic = Some((period, count - 1)); @@ -1137,7 +1311,10 @@ impl Pallet { } let wake = now.saturating_add(period); match Self::place_task(wake, task) { - Ok(_) => {}, + Ok(new_address) => + if let Some(retry_config) = maybe_retry_config { + Retries::::insert(new_address, retry_config); + }, Err((_, task)) => { // TODO: Leave task in storage somewhere for it to be rescheduled // manually. @@ -1192,6 +1369,70 @@ impl Pallet { let _ = weight.try_consume(call_weight); Ok(result) } + + /// Check if a task has a retry configuration in place and, if so, try to reschedule it. + /// + /// Possible causes for failure to schedule a retry for a task: + /// - there wasn't enough weight to run the task reschedule logic + /// - there was no retry configuration in place + /// - there were no more retry attempts left + /// - the agenda was full. + fn schedule_retry( + weight: &mut WeightMeter, + now: BlockNumberFor, + when: BlockNumberFor, + agenda_index: u32, + task: &ScheduledOf, + retry_config: RetryConfig>, + ) { + if weight + .try_consume(T::WeightInfo::schedule_retry(T::MaxScheduledPerBlock::get())) + .is_err() + { + Self::deposit_event(Event::RetryFailed { + task: (when, agenda_index), + id: task.maybe_id, + }); + return; + } + + let RetryConfig { total_retries, mut remaining, period } = retry_config; + remaining = match remaining.checked_sub(1) { + Some(n) => n, + None => return, + }; + let wake = now.saturating_add(period); + match Self::place_task(wake, task.as_retry()) { + Ok(address) => { + // Reinsert the retry config to the new address of the task after it was + // placed. + Retries::::insert(address, RetryConfig { total_retries, remaining, period }); + }, + Err((_, task)) => { + // TODO: Leave task in storage somewhere for it to be + // rescheduled manually. + T::Preimages::drop(&task.call); + Self::deposit_event(Event::RetryFailed { + task: (when, agenda_index), + id: task.maybe_id, + }); + }, + } + } + + /// Ensure that `left` has at least the same level of privilege or higher than `right`. + /// + /// Returns an error if `left` has a lower level of privilege or the two cannot be compared. + fn ensure_privilege( + left: &::PalletsOrigin, + right: &::PalletsOrigin, + ) -> Result<(), DispatchError> { + if matches!(T::OriginPrivilegeCmp::cmp_privilege(left, right), Some(Ordering::Less) | None) + { + return Err(BadOrigin.into()); + } + Ok(()) + } } impl schedule::v2::Anon, ::RuntimeCall, T::PalletsOrigin> diff --git a/substrate/frame/scheduler/src/migration.rs b/substrate/frame/scheduler/src/migration.rs index 76e2e04b49cc6fc7d9f886090de4df44911c0333..c2e956035a767031ceb64e4e0d42cebfb527e64d 100644 --- a/substrate/frame/scheduler/src/migration.rs +++ b/substrate/frame/scheduler/src/migration.rs @@ -81,7 +81,7 @@ pub mod v3 { StorageMap, Twox64Concat, Vec, TaskAddress>>; /// Migrate the scheduler pallet from V3 to V4. - pub struct MigrateToV4(sp_std::marker::PhantomData); + pub struct MigrateToV4(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV4 { #[cfg(feature = "try-runtime")] @@ -194,7 +194,7 @@ pub mod v4 { /// /// This should be run on a scheduler that does not have /// since it piles up `None`-only agendas. This does not modify the pallet version. - pub struct CleanupAgendas(sp_std::marker::PhantomData); + pub struct CleanupAgendas(core::marker::PhantomData); impl OnRuntimeUpgrade for CleanupAgendas { #[cfg(feature = "try-runtime")] diff --git a/substrate/frame/scheduler/src/mock.rs b/substrate/frame/scheduler/src/mock.rs index d22b9fcf8d99e35ecc7a39ff2097676eb788eee6..bf7dac0d53ae2596f6e9672e1753d96879b7c2be 100644 --- a/substrate/frame/scheduler/src/mock.rs +++ b/substrate/frame/scheduler/src/mock.rs @@ -22,17 +22,10 @@ use super::*; use crate as scheduler; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{ - ConstU32, ConstU64, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize, - }, - weights::constants::RocksDbWeight, + traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize}, }; use frame_system::{EnsureRoot, EnsureSignedBy}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, Perbill, -}; +use sp_runtime::{BuildStorage, Perbill}; // Logger module to track execution. #[frame_support::pallet] @@ -51,6 +44,17 @@ pub mod logger { #[pallet::pallet] pub struct Pallet(_); + #[pallet::storage] + pub type Threshold = StorageValue<_, (BlockNumberFor, BlockNumberFor)>; + + #[pallet::error] + pub enum Error { + /// Under the threshold. + TooEarly, + /// Over the threshold. + TooLate, + } + #[pallet::hooks] impl Hooks> for Pallet {} @@ -89,6 +93,20 @@ pub mod logger { }); Ok(()) } + + #[pallet::call_index(2)] + #[pallet::weight(*weight)] + pub fn timed_log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { + let now = frame_system::Pallet::::block_number(); + let (start, end) = Threshold::::get().unwrap_or((0u32.into(), u32::MAX.into())); + ensure!(now >= start, Error::::TooEarly); + ensure!(now <= end, Error::::TooLate); + Self::deposit_event(Event::Logged(i, weight)); + Log::mutate(|log| { + log.push((origin.caller().clone(), i)); + }); + Ok(()) + } } } @@ -122,28 +140,7 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl system::Config for Test { type BaseCallFilter = BaseFilter; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl logger::Config for Test { type RuntimeEvent = RuntimeEvent; @@ -198,6 +195,21 @@ impl WeightInfo for TestWeightInfo { fn cancel_named(_s: u32) -> Weight { Weight::from_parts(50, 0) } + fn schedule_retry(_s: u32) -> Weight { + Weight::from_parts(100000, 0) + } + fn set_retry() -> Weight { + Weight::from_parts(50, 0) + } + fn set_retry_named() -> Weight { + Weight::from_parts(50, 0) + } + fn cancel_retry() -> Weight { + Weight::from_parts(50, 0) + } + fn cancel_retry_named() -> Weight { + Weight::from_parts(50, 0) + } } parameter_types! { pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) * diff --git a/substrate/frame/scheduler/src/tests.rs b/substrate/frame/scheduler/src/tests.rs index 1bf8b3e5f3a03bd2c9d233a24351d1ae2d923c45..1ed2ca9e2f36d0b3bf4b3a02983ab1ab7539c30b 100644 --- a/substrate/frame/scheduler/src/tests.rs +++ b/substrate/frame/scheduler/src/tests.rs @@ -19,7 +19,8 @@ use super::*; use crate::mock::{ - logger, new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *, + logger::{self, Threshold}, + new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *, }; use frame_support::{ assert_err, assert_noop, assert_ok, @@ -179,6 +180,865 @@ fn periodic_scheduling_works() { }); } +#[test] +fn retry_scheduling_works() { + new_test_ext().execute_with(|| { + // task fails until block 8 is reached + Threshold::::put((8, 100)); + // task 42 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + assert!(Agenda::::get(4)[0].is_some()); + // retry 10 times every 3 blocks + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 3)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(4)[0].is_some()); + // task should be retried in block 7 + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(6); + assert!(Agenda::::get(7)[0].is_some()); + assert!(logger::log().is_empty()); + // task still fails, should be retried in block 10 + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(8); + assert!(Agenda::::get(10)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(9); + assert!(logger::log().is_empty()); + assert_eq!(Retries::::iter().count(), 1); + // finally it should succeed + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(11); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(12); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn named_retry_scheduling_works() { + new_test_ext().execute_with(|| { + // task fails until block 8 is reached + Threshold::::put((8, 100)); + // task 42 at #4 + let call = RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0), + }); + assert_eq!( + Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + ) + .unwrap(), + (4, 0) + ); + assert!(Agenda::::get(4)[0].is_some()); + // retry 10 times every 3 blocks + assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 3)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(4)[0].is_some()); + // task should be retried in block 7 + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(6); + assert!(Agenda::::get(7)[0].is_some()); + assert!(logger::log().is_empty()); + // task still fails, should be retried in block 10 + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(8); + assert!(Agenda::::get(10)[0].is_some()); + assert!(logger::log().is_empty()); + run_to_block(9); + assert!(logger::log().is_empty()); + assert_eq!(Retries::::iter().count(), 1); + // finally it should succeed + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(11); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(12); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn retry_scheduling_multiple_tasks_works() { + new_test_ext().execute_with(|| { + // task fails until block 8 is reached + Threshold::::put((8, 100)); + // task 20 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 20, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + // task 42 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert_eq!(Agenda::::get(4).len(), 2); + // task 20 will be retried 3 times every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 3, 1)); + // task 42 will be retried 10 times every 3 blocks + assert_ok!(Scheduler::set_retry(root().into(), (4, 1), 10, 3)); + assert_eq!(Retries::::iter().count(), 2); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_eq!(Agenda::::get(4).len(), 2); + // both tasks fail + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + // 20 is rescheduled for next block + assert_eq!(Agenda::::get(5).len(), 1); + // 42 is rescheduled for block 7 + assert_eq!(Agenda::::get(7).len(), 1); + assert!(logger::log().is_empty()); + // 20 still fails + run_to_block(5); + // 20 rescheduled for next block + assert_eq!(Agenda::::get(6).len(), 1); + assert_eq!(Agenda::::get(7).len(), 1); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + // 20 still fails + run_to_block(6); + // rescheduled for next block together with 42 + assert_eq!(Agenda::::get(7).len(), 2); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + // both tasks will fail, for 20 it was the last retry so it's dropped + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(8).is_empty()); + // 42 is rescheduled for block 10 + assert_eq!(Agenda::::get(10).len(), 1); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + run_to_block(8); + assert_eq!(Agenda::::get(10).len(), 1); + assert!(logger::log().is_empty()); + run_to_block(9); + assert!(logger::log().is_empty()); + assert_eq!(Retries::::iter().count(), 1); + // 42 runs successfully + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(11); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(12); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn retry_scheduling_multiple_named_tasks_works() { + new_test_ext().execute_with(|| { + // task fails until we reach block 8 + Threshold::::put((8, 100)); + // task 20 at #4 + assert_ok!(Scheduler::do_schedule_named( + [20u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 20, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + // task 42 at #4 + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert_eq!(Agenda::::get(4).len(), 2); + // task 20 will be retried 3 times every block + assert_ok!(Scheduler::set_retry_named(root().into(), [20u8; 32], 3, 1)); + // task 42 will be retried 10 times every 3 block + assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 3)); + assert_eq!(Retries::::iter().count(), 2); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_eq!(Agenda::::get(4).len(), 2); + // both tasks fail + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + // 42 is rescheduled for block 7 + assert_eq!(Agenda::::get(7).len(), 1); + // 20 is rescheduled for next block + assert_eq!(Agenda::::get(5).len(), 1); + assert!(logger::log().is_empty()); + // 20 still fails + run_to_block(5); + // 20 rescheduled for next block + assert_eq!(Agenda::::get(6).len(), 1); + assert_eq!(Agenda::::get(7).len(), 1); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + // 20 still fails + run_to_block(6); + // 20 rescheduled for next block together with 42 + assert_eq!(Agenda::::get(7).len(), 2); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + // both tasks will fail, for 20 it was the last retry so it's dropped + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(8).is_empty()); + // 42 is rescheduled for block 10 + assert_eq!(Agenda::::get(10).len(), 1); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + run_to_block(8); + assert_eq!(Agenda::::get(10).len(), 1); + assert!(logger::log().is_empty()); + run_to_block(9); + assert!(logger::log().is_empty()); + assert_eq!(Retries::::iter().count(), 1); + // 42 runs successfully + run_to_block(10); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(11); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(12); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + run_to_block(100); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + }); +} + +#[test] +fn retry_scheduling_with_period_works() { + new_test_ext().execute_with(|| { + // tasks fail until we reach block 4 and after we're past block 8 + Threshold::::put((4, 8)); + // task 42 at #4, every 3 blocks, 6 times + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + Some((3, 6)), + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // 42 will be retried 10 times every 2 blocks + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 2)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(4)[0].is_some()); + // 42 runs successfully once, it will run again at block 7 + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // nothing changed + run_to_block(6); + assert!(Agenda::::get(7)[0].is_some()); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 42 runs successfully again, it will run again at block 10 + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(9); + assert!(Agenda::::get(10)[0].is_some()); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 has 10 retries left out of a total of 10 + assert_eq!(Retries::::get((10, 0)).unwrap().remaining, 10); + // 42 will fail because we're outside the set threshold (block number in `4..8`), so it + // should be retried in 2 blocks (at block 12) + run_to_block(10); + // should be queued for the normal period of 3 blocks + assert!(Agenda::::get(13)[0].is_some()); + // should also be queued to be retried in 2 blocks + assert!(Agenda::::get(12)[0].is_some()); + // 42 has consumed one retry attempt + assert_eq!(Retries::::get((12, 0)).unwrap().remaining, 9); + assert_eq!(Retries::::get((13, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 will fail again + run_to_block(12); + // should still be queued for the normal period + assert!(Agenda::::get(13)[0].is_some()); + // should be queued to be retried in 2 blocks + assert!(Agenda::::get(14)[0].is_some()); + // 42 has consumed another retry attempt + assert_eq!(Retries::::get((14, 0)).unwrap().remaining, 8); + assert_eq!(Retries::::get((13, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 will fail for the regular periodic run + run_to_block(13); + // should still be queued for the normal period + assert!(Agenda::::get(16)[0].is_some()); + // should still be queued to be retried next block + assert!(Agenda::::get(14)[0].is_some()); + // 42 consumed another periodic run, which failed, so another retry is queued for block 15 + assert!(Agenda::::get(16)[0].as_ref().unwrap().maybe_periodic.is_some()); + assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert!(Agenda::::get(14)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert_eq!(Retries::::iter().count(), 3); + assert!(Retries::::get((14, 0)).unwrap().remaining == 8); + assert!(Retries::::get((15, 0)).unwrap().remaining == 9); + assert!(Retries::::get((16, 0)).unwrap().remaining == 10); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // change the threshold to allow the task to succeed + Threshold::::put((14, 100)); + // first retry should now succeed + run_to_block(14); + assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); + assert!(Agenda::::get(16)[0].is_some()); + assert_eq!(Retries::::get((15, 0)).unwrap().remaining, 9); + assert_eq!(Retries::::get((16, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + // second retry should also succeed + run_to_block(15); + assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); + assert!(Agenda::::get(16)[0].is_some()); + assert!(Agenda::::get(17).is_empty()); + assert_eq!(Retries::::get((16, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + // normal periodic run on block 16 will succeed + run_to_block(16); + // next periodic run at block 19 + assert!(Agenda::::get(19)[0].is_some()); + assert!(Agenda::::get(18).is_empty()); + assert!(Agenda::::get(17).is_empty()); + assert_eq!(Retries::::get((19, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!( + logger::log(), + vec![ + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32) + ] + ); + // final periodic run on block 19 will succeed + run_to_block(19); + // next periodic run at block 19 + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert_eq!( + logger::log(), + vec![ + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32) + ] + ); + }); +} + +#[test] +fn named_retry_scheduling_with_period_works() { + new_test_ext().execute_with(|| { + // tasks fail until we reach block 4 and after we're past block 8 + Threshold::::put((4, 8)); + // task 42 at #4, every 3 blocks, 6 times + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(4), + Some((3, 6)), + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // 42 will be retried 10 times every 2 blocks + assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 2)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(4)[0].is_some()); + // 42 runs successfully once, it will run again at block 7 + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // nothing changed + run_to_block(6); + assert!(Agenda::::get(7)[0].is_some()); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 42 runs successfully again, it will run again at block 10 + run_to_block(7); + assert!(Agenda::::get(7).is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + run_to_block(9); + assert!(Agenda::::get(10)[0].is_some()); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 has 10 retries left out of a total of 10 + assert_eq!(Retries::::get((10, 0)).unwrap().remaining, 10); + // 42 will fail because we're outside the set threshold (block number in `4..8`), so it + // should be retried in 2 blocks (at block 12) + run_to_block(10); + // should be queued for the normal period of 3 blocks + assert!(Agenda::::get(13)[0].is_some()); + // should also be queued to be retried in 2 blocks + assert!(Agenda::::get(12)[0].is_some()); + // 42 has consumed one retry attempt + assert_eq!(Retries::::get((12, 0)).unwrap().remaining, 9); + assert_eq!(Retries::::get((13, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (13, 0)); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 will fail again + run_to_block(12); + // should still be queued for the normal period + assert!(Agenda::::get(13)[0].is_some()); + // should be queued to be retried in 2 blocks + assert!(Agenda::::get(14)[0].is_some()); + // 42 has consumed another retry attempt + assert_eq!(Retries::::get((14, 0)).unwrap().remaining, 8); + assert_eq!(Retries::::get((13, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // 42 will fail for the regular periodic run + run_to_block(13); + // should still be queued for the normal period + assert!(Agenda::::get(16)[0].is_some()); + // should still be queued to be retried next block + assert!(Agenda::::get(14)[0].is_some()); + // 42 consumed another periodic run, which failed, so another retry is queued for block 15 + assert!(Agenda::::get(16)[0].as_ref().unwrap().maybe_periodic.is_some()); + assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert!(Agenda::::get(14)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert_eq!(Retries::::iter().count(), 3); + assert!(Retries::::get((14, 0)).unwrap().remaining == 8); + assert!(Retries::::get((15, 0)).unwrap().remaining == 9); + assert!(Retries::::get((16, 0)).unwrap().remaining == 10); + assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (16, 0)); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + // change the threshold to allow the task to succeed + Threshold::::put((14, 100)); + // first retry should now succeed + run_to_block(14); + assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); + assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); + assert!(Agenda::::get(16)[0].is_some()); + assert_eq!(Retries::::get((15, 0)).unwrap().remaining, 9); + assert_eq!(Retries::::get((16, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + // second retry should also succeed + run_to_block(15); + assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); + assert!(Agenda::::get(16)[0].is_some()); + assert!(Agenda::::get(17).is_empty()); + assert_eq!(Retries::::get((16, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (16, 0)); + assert_eq!( + logger::log(), + vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] + ); + // normal periodic run on block 16 will succeed + run_to_block(16); + // next periodic run at block 19 + assert!(Agenda::::get(19)[0].is_some()); + assert!(Agenda::::get(18).is_empty()); + assert!(Agenda::::get(17).is_empty()); + assert_eq!(Retries::::get((19, 0)).unwrap().remaining, 10); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (19, 0)); + assert_eq!( + logger::log(), + vec![ + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32) + ] + ); + // final periodic run on block 19 will succeed + run_to_block(19); + // next periodic run at block 19 + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert_eq!(Lookup::::iter().count(), 0); + assert_eq!( + logger::log(), + vec![ + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32), + (root(), 42u32) + ] + ); + }); +} + +#[test] +fn retry_scheduling_expires() { + new_test_ext().execute_with(|| { + // task will fail if we're past block 3 + Threshold::::put((1, 3)); + // task 42 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + assert!(Agenda::::get(4)[0].is_some()); + // task 42 will be retried 3 times every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 3, 1)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(3); + assert!(logger::log().is_empty()); + // task 42 is scheduled for next block + assert!(Agenda::::get(4)[0].is_some()); + // task fails because we're past block 3 + run_to_block(4); + // task is scheduled for next block + assert!(Agenda::::get(4).is_empty()); + assert!(Agenda::::get(5)[0].is_some()); + // one retry attempt is consumed + assert_eq!(Retries::::get((5, 0)).unwrap().remaining, 2); + assert!(logger::log().is_empty()); + // task fails again + run_to_block(5); + // task is scheduled for next block + assert!(Agenda::::get(5).is_empty()); + assert!(Agenda::::get(6)[0].is_some()); + // another retry attempt is consumed + assert_eq!(Retries::::get((6, 0)).unwrap().remaining, 1); + assert!(logger::log().is_empty()); + // task fails again + run_to_block(6); + // task is scheduled for next block + assert!(Agenda::::get(6).is_empty()); + assert!(Agenda::::get(7)[0].is_some()); + // another retry attempt is consumed + assert_eq!(Retries::::get((7, 0)).unwrap().remaining, 0); + assert!(logger::log().is_empty()); + // task fails again + run_to_block(7); + // task ran out of retries so it gets dropped + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert!(logger::log().is_empty()); + }); +} + +#[test] +fn set_retry_bad_origin() { + new_test_ext().execute_with(|| { + // task 42 at #4 with account 101 as origin + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + 101.into(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // try to change the retry config with a different (non-root) account + let res: Result<(), DispatchError> = + Scheduler::set_retry(RuntimeOrigin::signed(102), (4, 0), 10, 2); + assert_eq!(res, Err(BadOrigin.into())); + }); +} + +#[test] +fn set_named_retry_bad_origin() { + new_test_ext().execute_with(|| { + // task 42 at #4 with account 101 as origin + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(4), + None, + 127, + 101.into(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // try to change the retry config with a different (non-root) account + let res: Result<(), DispatchError> = + Scheduler::set_retry_named(RuntimeOrigin::signed(102), [42u8; 32], 10, 2); + assert_eq!(res, Err(BadOrigin.into())); + }); +} + +#[test] +fn set_retry_works() { + new_test_ext().execute_with(|| { + // task 42 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // make sure the retry configuration was stored + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 2)); + assert_eq!( + Retries::::get((4, 0)), + Some(RetryConfig { total_retries: 10, remaining: 10, period: 2 }) + ); + }); +} + +#[test] +fn set_named_retry_works() { + new_test_ext().execute_with(|| { + // task 42 at #4 with account 101 as origin + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(4)[0].is_some()); + // make sure the retry configuration was stored + assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 2)); + let address = Lookup::::get([42u8; 32]).unwrap(); + assert_eq!( + Retries::::get(address), + Some(RetryConfig { total_retries: 10, remaining: 10, period: 2 }) + ); + }); +} + +#[test] +fn retry_periodic_full_cycle() { + new_test_ext().execute_with(|| { + // tasks fail after we pass block 1000 + Threshold::::put((1, 1000)); + // task 42 at #4, every 100 blocks, 4 times + assert_ok!(Scheduler::do_schedule_named( + [42u8; 32], + DispatchTime::At(10), + Some((100, 4)), + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert!(Agenda::::get(10)[0].is_some()); + // 42 will be retried 2 times every block + assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 2, 1)); + assert_eq!(Retries::::iter().count(), 1); + run_to_block(9); + assert!(logger::log().is_empty()); + assert!(Agenda::::get(10)[0].is_some()); + // 42 runs successfully once, it will run again at block 110 + run_to_block(10); + assert!(Agenda::::get(10).is_empty()); + assert!(Agenda::::get(110)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // nothing changed + run_to_block(109); + assert!(Agenda::::get(110)[0].is_some()); + // original task still has 2 remaining retries + assert_eq!(Retries::::get((110, 0)).unwrap().remaining, 2); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // make 42 fail next block + Threshold::::put((1, 2)); + // 42 will fail because we're outside the set threshold (block number in `1..2`), so it + // should be retried next block (at block 111) + run_to_block(110); + // should be queued for the normal period of 100 blocks + assert!(Agenda::::get(210)[0].is_some()); + // should also be queued to be retried next block + assert!(Agenda::::get(111)[0].is_some()); + // 42 retry clone has consumed one retry attempt + assert_eq!(Retries::::get((111, 0)).unwrap().remaining, 1); + // 42 original task still has the original remaining attempts + assert_eq!(Retries::::get((210, 0)).unwrap().remaining, 2); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 42 retry will fail again + run_to_block(111); + // should still be queued for the normal period + assert!(Agenda::::get(210)[0].is_some()); + // should be queued to be retried next block + assert!(Agenda::::get(112)[0].is_some()); + // 42 has consumed another retry attempt + assert_eq!(Retries::::get((210, 0)).unwrap().remaining, 2); + assert_eq!(Retries::::get((112, 0)).unwrap().remaining, 0); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 42 retry will fail again + run_to_block(112); + // should still be queued for the normal period + assert!(Agenda::::get(210)[0].is_some()); + // 42 retry clone ran out of retries, must have been evicted + assert_eq!(Agenda::::iter().count(), 1); + + // advance + run_to_block(209); + // should still be queued for the normal period + assert!(Agenda::::get(210)[0].is_some()); + // 42 retry clone ran out of retries, must have been evicted + assert_eq!(Agenda::::iter().count(), 1); + // 42 should fail again and should spawn another retry clone + run_to_block(210); + // should be queued for the normal period of 100 blocks + assert!(Agenda::::get(310)[0].is_some()); + // should also be queued to be retried next block + assert!(Agenda::::get(211)[0].is_some()); + // 42 retry clone has consumed one retry attempt + assert_eq!(Retries::::get((211, 0)).unwrap().remaining, 1); + // 42 original task still has the original remaining attempts + assert_eq!(Retries::::get((310, 0)).unwrap().remaining, 2); + assert_eq!(Retries::::iter().count(), 2); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // make 42 run successfully again + Threshold::::put((1, 1000)); + // 42 retry clone should now succeed + run_to_block(211); + // should be queued for the normal period of 100 blocks + assert!(Agenda::::get(310)[0].is_some()); + // retry was successful, retry task should have been discarded + assert_eq!(Agenda::::iter().count(), 1); + // 42 original task still has the original remaining attempts + assert_eq!(Retries::::get((310, 0)).unwrap().remaining, 2); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); + + // fast forward to the last periodic run of 42 + run_to_block(310); + // 42 was successful, the period ended as this was the 4th scheduled periodic run so 42 must + // have been discarded + assert_eq!(Agenda::::iter().count(), 0); + // agenda is empty so no retries should exist + assert_eq!(Retries::::iter().count(), 0); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); + }); +} + #[test] fn reschedule_works() { new_test_ext().execute_with(|| { @@ -430,6 +1290,117 @@ fn scheduler_respects_weight_limits() { }); } +#[test] +fn retry_respects_weight_limits() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + // schedule 42 + let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: max_weight / 3 * 2 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(8), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + // schedule 20 with a call that will fail until we reach block 8 + Threshold::::put((8, 100)); + let call = RuntimeCall::Logger(LoggerCall::timed_log { i: 20, weight: max_weight / 3 * 2 }); + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + // set a retry config for 20 for 10 retries every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); + // 20 should fail and be retried later + run_to_block(4); + assert!(Agenda::::get(5)[0].is_some()); + assert!(Agenda::::get(8)[0].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + // 20 still fails but is scheduled next block together with 42 + run_to_block(7); + assert_eq!(Agenda::::get(8).len(), 2); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + // 20 and 42 do not fit together + // 42 is executed as it was first in the queue + // 20 is still on the 8th block's agenda + run_to_block(8); + assert!(Agenda::::get(8)[0].is_none()); + assert!(Agenda::::get(8)[1].is_some()); + assert_eq!(Retries::::iter().count(), 1); + assert_eq!(logger::log(), vec![(root(), 42u32)]); + // 20 is executed and the schedule is cleared + run_to_block(9); + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 20u32)]); + }); +} + +#[test] +fn try_schedule_retry_respects_weight_limits() { + let max_weight: Weight = ::MaximumWeight::get(); + new_test_ext().execute_with(|| { + let service_agendas_weight = ::WeightInfo::service_agendas_base(); + let service_agenda_weight = ::WeightInfo::service_agenda_base( + ::MaxScheduledPerBlock::get(), + ); + let actual_service_agenda_weight = ::WeightInfo::service_agenda_base(1); + // Some weight for `service_agenda` will be refunded, so we need to make sure the weight + // `try_schedule_retry` is going to ask for is greater than this difference, and we take a + // safety factor of 10 to make sure we're over that limit. + let meter = WeightMeter::with_limit( + ::WeightInfo::schedule_retry( + ::MaxScheduledPerBlock::get(), + ) / 10, + ); + assert!(meter.can_consume(service_agenda_weight - actual_service_agenda_weight)); + + let reference_call = + RuntimeCall::Logger(LoggerCall::timed_log { i: 20, weight: max_weight / 3 * 2 }); + let bounded = ::Preimages::bound(reference_call).unwrap(); + let base_weight = ::WeightInfo::service_task( + bounded.lookup_len().map(|x| x as usize), + false, + false, + ); + // we make the call cost enough so that all checks have enough weight to run aside from + // `try_schedule_retry` + let call_weight = max_weight - service_agendas_weight - service_agenda_weight - base_weight; + let call = RuntimeCall::Logger(LoggerCall::timed_log { i: 20, weight: call_weight }); + // schedule 20 with a call that will fail until we reach block 8 + Threshold::::put((8, 100)); + + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(call).unwrap(), + )); + // set a retry config for 20 for 10 retries every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); + // 20 should fail and, because of insufficient weight, it should not be scheduled again + run_to_block(4); + // nothing else should be scheduled + assert_eq!(Agenda::::iter().count(), 0); + assert_eq!(Retries::::iter().count(), 0); + assert_eq!(logger::log(), vec![]); + // check the `RetryFailed` event happened + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = + Event::RetryFailed { task: (4, 0), id: None }.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); + }); +} + /// Permanently overweight calls are not deleted but also not executed. #[test] fn scheduler_does_not_delete_permanently_overweight_call() { @@ -877,6 +1848,134 @@ fn should_check_origin_for_cancel() { }); } +#[test] +fn cancel_removes_retry_entry() { + new_test_ext().execute_with(|| { + // task fails until block 99 is reached + Threshold::::put((99, 100)); + // task 20 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 20, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + // named task 42 at #4 + assert_ok!(Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert_eq!(Agenda::::get(4).len(), 2); + // task 20 will be retried 3 times every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); + // task 42 will be retried 10 times every 3 blocks + assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); + assert_eq!(Retries::::iter().count(), 2); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_eq!(Agenda::::get(4).len(), 2); + // both tasks fail + run_to_block(4); + assert!(Agenda::::get(4).is_empty()); + // 42 and 20 are rescheduled for next block + assert_eq!(Agenda::::get(5).len(), 2); + assert!(logger::log().is_empty()); + // 42 and 20 still fail + run_to_block(5); + // 42 and 20 rescheduled for next block + assert_eq!(Agenda::::get(6).len(), 2); + assert_eq!(Retries::::iter().count(), 2); + assert!(logger::log().is_empty()); + + // even though 42 is being retried, the tasks scheduled for retries are not named + assert_eq!(Lookup::::iter().count(), 0); + assert!(Scheduler::cancel(root().into(), 6, 0).is_ok()); + + // 20 is removed, 42 still fails + run_to_block(6); + // 42 rescheduled for next block + assert_eq!(Agenda::::get(7).len(), 1); + // 20's retry entry is removed + assert!(!Retries::::contains_key((4, 0))); + assert_eq!(Retries::::iter().count(), 1); + assert!(logger::log().is_empty()); + + assert!(Scheduler::cancel(root().into(), 7, 0).is_ok()); + + // both tasks are canceled, everything is removed now + run_to_block(7); + assert!(Agenda::::get(8).is_empty()); + assert_eq!(Retries::::iter().count(), 0); + }); +} + +#[test] +fn cancel_retries_works() { + new_test_ext().execute_with(|| { + // task fails until block 99 is reached + Threshold::::put((99, 100)); + // task 20 at #4 + assert_ok!(Scheduler::do_schedule( + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 20, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + // named task 42 at #4 + assert_ok!(Scheduler::do_schedule_named( + [1u8; 32], + DispatchTime::At(4), + None, + 127, + root(), + Preimage::bound(RuntimeCall::Logger(logger::Call::timed_log { + i: 42, + weight: Weight::from_parts(10, 0) + })) + .unwrap() + )); + + assert_eq!(Agenda::::get(4).len(), 2); + // task 20 will be retried 3 times every block + assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); + // task 42 will be retried 10 times every 3 blocks + assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); + assert_eq!(Retries::::iter().count(), 2); + run_to_block(3); + assert!(logger::log().is_empty()); + assert_eq!(Agenda::::get(4).len(), 2); + // cancel the retry config for 20 + assert_ok!(Scheduler::cancel_retry(root().into(), (4, 0))); + assert_eq!(Retries::::iter().count(), 1); + // cancel the retry config for 42 + assert_ok!(Scheduler::cancel_retry_named(root().into(), [1u8; 32])); + assert_eq!(Retries::::iter().count(), 0); + run_to_block(4); + // both tasks failed and there are no more retries, so they are evicted + assert_eq!(Agenda::::get(4).len(), 0); + assert_eq!(Retries::::iter().count(), 0); + }); +} + #[test] fn migration_to_v4_works() { new_test_ext().execute_with(|| { @@ -1054,6 +2153,8 @@ fn test_migrate_origin() { match self { 3u32 => system::RawOrigin::Root.into(), 2u32 => system::RawOrigin::None.into(), + 101u32 => system::RawOrigin::Signed(101).into(), + 102u32 => system::RawOrigin::Signed(102).into(), _ => unreachable!("test make no use of it"), } } diff --git a/substrate/frame/scheduler/src/weights.rs b/substrate/frame/scheduler/src/weights.rs index 58d711862591d033b66251def364acd5ebbac7a7..9b7e5405a1b5bdd3945f0935801f79843cd0d78a 100644 --- a/substrate/frame/scheduler/src/weights.rs +++ b/substrate/frame/scheduler/src/weights.rs @@ -15,32 +15,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_scheduler +//! Autogenerated weights for `pallet_scheduler` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! HOSTNAME: `runner-grjcggob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_scheduler -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/scheduler/src/weights.rs -// --header=./HEADER-APACHE2 -// --template=./.maintain/frame-weight-template.hbs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_scheduler +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/scheduler/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,7 +47,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; -/// Weight functions needed for pallet_scheduler. +/// Weight functions needed for `pallet_scheduler`. pub trait WeightInfo { fn service_agendas_base() -> Weight; fn service_agenda_base(s: u32, ) -> Weight; @@ -64,33 +61,38 @@ pub trait WeightInfo { fn cancel(s: u32, ) -> Weight; fn schedule_named(s: u32, ) -> Weight; fn cancel_named(s: u32, ) -> Weight; + fn schedule_retry(s: u32, ) -> Weight; + fn set_retry() -> Weight; + fn set_retry_named() -> Weight; + fn cancel_retry() -> Weight; + fn cancel_retry_named() -> Weight; } -/// Weights for pallet_scheduler using the Substrate node and recommended hardware. +/// Weights for `pallet_scheduler` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: // Measured: `31` // Estimated: `1489` - // Minimum execution time: 3_991_000 picoseconds. - Weight::from_parts(4_174_000, 1489) + // Minimum execution time: 3_040_000 picoseconds. + Weight::from_parts(3_202_000, 1489) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 512]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 3_581_000 picoseconds. - Weight::from_parts(7_413_174, 110487) - // Standard Error: 971 - .saturating_add(Weight::from_parts(348_077, 0).saturating_mul(s.into())) + // Minimum execution time: 3_462_000 picoseconds. + Weight::from_parts(6_262_125, 110487) + // Standard Error: 536 + .saturating_add(Weight::from_parts(332_570, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -98,145 +100,226 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_250_000 picoseconds. - Weight::from_parts(5_549_000, 0) + // Minimum execution time: 3_425_000 picoseconds. + Weight::from_parts(3_680_000, 0) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `179 + s * (1 ±0)` - // Estimated: `3644 + s * (1 ±0)` - // Minimum execution time: 20_089_000 picoseconds. - Weight::from_parts(20_376_000, 3644) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_170, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `246 + s * (1 ±0)` + // Estimated: `3711 + s * (1 ±0)` + // Minimum execution time: 17_564_000 picoseconds. + Weight::from_parts(17_887_000, 3711) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_253, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_998_000 picoseconds. - Weight::from_parts(7_303_000, 0) + // Minimum execution time: 4_934_000 picoseconds. + Weight::from_parts(5_275_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn service_task_periodic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_078_000 picoseconds. - Weight::from_parts(5_315_000, 0) + // Minimum execution time: 3_348_000 picoseconds. + Weight::from_parts(3_561_000, 0) } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_228_000 picoseconds. - Weight::from_parts(2_352_000, 0) + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 6_395_000 picoseconds. + Weight::from_parts(6_642_000, 3997) + .saturating_add(T::DbWeight::get().reads(2_u64)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_226_000 picoseconds. - Weight::from_parts(2_371_000, 0) + // Minimum execution time: 2_167_000 picoseconds. + Weight::from_parts(2_266_000, 0) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 12_683_000 picoseconds. - Weight::from_parts(16_951_846, 110487) - // Standard Error: 1_046 - .saturating_add(Weight::from_parts(380_842, 0).saturating_mul(s.into())) + // Minimum execution time: 10_009_000 picoseconds. + Weight::from_parts(13_565_985, 110487) + // Standard Error: 575 + .saturating_add(Weight::from_parts(354_760, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 16_201_000 picoseconds. - Weight::from_parts(18_259_422, 110487) - // Standard Error: 1_344 - .saturating_add(Weight::from_parts(545_863, 0).saturating_mul(s.into())) + // Minimum execution time: 14_048_000 picoseconds. + Weight::from_parts(15_141_696, 110487) + // Standard Error: 1_082 + .saturating_add(Weight::from_parts(533_390, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `596 + s * (178 ±0)` // Estimated: `110487` - // Minimum execution time: 16_180_000 picoseconds. - Weight::from_parts(25_128_925, 110487) - // Standard Error: 1_118 - .saturating_add(Weight::from_parts(375_631, 0).saturating_mul(s.into())) + // Minimum execution time: 12_902_000 picoseconds. + Weight::from_parts(18_957_156, 110487) + // Standard Error: 792 + .saturating_add(Weight::from_parts(361_909, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `709 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 18_244_000 picoseconds. - Weight::from_parts(21_439_366, 110487) - // Standard Error: 1_084 - .saturating_add(Weight::from_parts(557_691, 0).saturating_mul(s.into())) + // Minimum execution time: 15_933_000 picoseconds. + Weight::from_parts(18_091_415, 110487) + // Standard Error: 779 + .saturating_add(Weight::from_parts(534_402, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 512]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `110487` + // Minimum execution time: 14_155_000 picoseconds. + Weight::from_parts(16_447_031, 110487) + // Standard Error: 233 + .saturating_add(Weight::from_parts(8_424, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 8_130_000 picoseconds. + Weight::from_parts(9_047_554, 110487) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `647 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 10_838_000 picoseconds. + Weight::from_parts(12_804_076, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 8_130_000 picoseconds. + Weight::from_parts(9_047_554, 110487) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `647 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 10_838_000 picoseconds. + Weight::from_parts(12_804_076, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } } -// For backwards compatibility and tests +// For backwards compatibility and tests. impl WeightInfo for () { - /// Storage: Scheduler IncompleteSince (r:1 w:1) - /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: `Scheduler::IncompleteSince` (r:1 w:1) + /// Proof: `Scheduler::IncompleteSince` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn service_agendas_base() -> Weight { // Proof Size summary in bytes: // Measured: `31` // Estimated: `1489` - // Minimum execution time: 3_991_000 picoseconds. - Weight::from_parts(4_174_000, 1489) + // Minimum execution time: 3_040_000 picoseconds. + Weight::from_parts(3_202_000, 1489) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 512]`. fn service_agenda_base(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 3_581_000 picoseconds. - Weight::from_parts(7_413_174, 110487) - // Standard Error: 971 - .saturating_add(Weight::from_parts(348_077, 0).saturating_mul(s.into())) + // Minimum execution time: 3_462_000 picoseconds. + Weight::from_parts(6_262_125, 110487) + // Standard Error: 536 + .saturating_add(Weight::from_parts(332_570, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -244,117 +327,198 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_250_000 picoseconds. - Weight::from_parts(5_549_000, 0) + // Minimum execution time: 3_425_000 picoseconds. + Weight::from_parts(3_680_000, 0) } - /// Storage: Preimage PreimageFor (r:1 w:1) - /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) - /// Storage: Preimage StatusFor (r:1 w:1) - /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: `Preimage::PreimageFor` (r:1 w:1) + /// Proof: `Preimage::PreimageFor` (`max_values`: None, `max_size`: Some(4194344), added: 4196819, mode: `Measured`) + /// Storage: `Preimage::StatusFor` (r:1 w:0) + /// Proof: `Preimage::StatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) + /// Storage: `Preimage::RequestStatusFor` (r:1 w:1) + /// Proof: `Preimage::RequestStatusFor` (`max_values`: None, `max_size`: Some(91), added: 2566, mode: `MaxEncodedLen`) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `179 + s * (1 ±0)` - // Estimated: `3644 + s * (1 ±0)` - // Minimum execution time: 20_089_000 picoseconds. - Weight::from_parts(20_376_000, 3644) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_170, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Measured: `246 + s * (1 ±0)` + // Estimated: `3711 + s * (1 ±0)` + // Minimum execution time: 17_564_000 picoseconds. + Weight::from_parts(17_887_000, 3711) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_253, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) fn service_task_named() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_998_000 picoseconds. - Weight::from_parts(7_303_000, 0) + // Minimum execution time: 4_934_000 picoseconds. + Weight::from_parts(5_275_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn service_task_periodic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_078_000 picoseconds. - Weight::from_parts(5_315_000, 0) + // Minimum execution time: 3_348_000 picoseconds. + Weight::from_parts(3_561_000, 0) } + /// Storage: `SafeMode::EnteredUntil` (r:1 w:0) + /// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `TxPause::PausedCalls` (r:1 w:0) + /// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`) fn execute_dispatch_signed() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_228_000 picoseconds. - Weight::from_parts(2_352_000, 0) + // Measured: `145` + // Estimated: `3997` + // Minimum execution time: 6_395_000 picoseconds. + Weight::from_parts(6_642_000, 3997) + .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn execute_dispatch_unsigned() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_226_000 picoseconds. - Weight::from_parts(2_371_000, 0) + // Minimum execution time: 2_167_000 picoseconds. + Weight::from_parts(2_266_000, 0) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 12_683_000 picoseconds. - Weight::from_parts(16_951_846, 110487) - // Standard Error: 1_046 - .saturating_add(Weight::from_parts(380_842, 0).saturating_mul(s.into())) + // Minimum execution time: 10_009_000 picoseconds. + Weight::from_parts(13_565_985, 110487) + // Standard Error: 575 + .saturating_add(Weight::from_parts(354_760, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) - /// Storage: Scheduler Lookup (r:0 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `81 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 16_201_000 picoseconds. - Weight::from_parts(18_259_422, 110487) - // Standard Error: 1_344 - .saturating_add(Weight::from_parts(545_863, 0).saturating_mul(s.into())) + // Minimum execution time: 14_048_000 picoseconds. + Weight::from_parts(15_141_696, 110487) + // Standard Error: 1_082 + .saturating_add(Weight::from_parts(533_390, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `596 + s * (178 ±0)` // Estimated: `110487` - // Minimum execution time: 16_180_000 picoseconds. - Weight::from_parts(25_128_925, 110487) - // Standard Error: 1_118 - .saturating_add(Weight::from_parts(375_631, 0).saturating_mul(s.into())) + // Minimum execution time: 12_902_000 picoseconds. + Weight::from_parts(18_957_156, 110487) + // Standard Error: 792 + .saturating_add(Weight::from_parts(361_909, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } - /// Storage: Scheduler Lookup (r:1 w:1) - /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) - /// Storage: Scheduler Agenda (r:1 w:1) - /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: `Scheduler::Lookup` (r:1 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `709 + s * (177 ±0)` // Estimated: `110487` - // Minimum execution time: 18_244_000 picoseconds. - Weight::from_parts(21_439_366, 110487) - // Standard Error: 1_084 - .saturating_add(Weight::from_parts(557_691, 0).saturating_mul(s.into())) + // Minimum execution time: 15_933_000 picoseconds. + Weight::from_parts(18_091_415, 110487) + // Standard Error: 779 + .saturating_add(Weight::from_parts(534_402, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: `Scheduler::Retries` (r:1 w:2) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:1) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Lookup` (r:0 w:1) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// The range of component `s` is `[1, 512]`. + fn schedule_retry(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `110487` + // Minimum execution time: 14_155_000 picoseconds. + Weight::from_parts(16_447_031, 110487) + // Standard Error: 233 + .saturating_add(Weight::from_parts(8_424, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 8_130_000 picoseconds. + Weight::from_parts(9_047_554, 110487) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn set_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `647 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 10_838_000 picoseconds. + Weight::from_parts(12_804_076, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry() -> Weight { + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 8_130_000 picoseconds. + Weight::from_parts(9_047_554, 110487) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Scheduler::Lookup` (r:1 w:0) + /// Proof: `Scheduler::Lookup` (`max_values`: None, `max_size`: Some(48), added: 2523, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Agenda` (r:1 w:0) + /// Proof: `Scheduler::Agenda` (`max_values`: None, `max_size`: Some(107022), added: 109497, mode: `MaxEncodedLen`) + /// Storage: `Scheduler::Retries` (r:0 w:1) + /// Proof: `Scheduler::Retries` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + fn cancel_retry_named() -> Weight { + // Proof Size summary in bytes: + // Measured: `647 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 10_838_000 picoseconds. + Weight::from_parts(12_804_076, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } diff --git a/substrate/frame/scored-pool/src/mock.rs b/substrate/frame/scored-pool/src/mock.rs index e767b49b983a33635ccae9086bc23fd9595f1231..6fba1bb3d5376ba13273b1133691537234791dca 100644 --- a/substrate/frame/scored-pool/src/mock.rs +++ b/substrate/frame/scored-pool/src/mock.rs @@ -25,12 +25,7 @@ use frame_support::{ traits::{ConstU32, ConstU64}, }; use frame_system::EnsureSignedBy; -use sp_core::H256; -use sp_runtime::{ - bounded_vec, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::{bounded_vec, BuildStorage}; type Block = frame_system::mocking::MockBlock; @@ -53,29 +48,8 @@ ord_parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/session/Cargo.toml b/substrate/frame/session/Cargo.toml index 91ffecbf7178ec6d241ba5f4f1d6d509c3aa307c..de041307f7022678235d98372bf7119eb4d48eaa 100644 --- a/substrate/frame/session/Cargo.toml +++ b/substrate/frame/session/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/session/src/lib.rs b/substrate/frame/session/src/lib.rs index 77f525e29f277ddfd2ff1de3042e73f0e0c455aa..d78c0fa1e33266f04b6b9b402b931c724e9f23ed 100644 --- a/substrate/frame/session/src/lib.rs +++ b/substrate/frame/session/src/lib.rs @@ -460,27 +460,13 @@ pub mod pallet { ); self.keys.iter().map(|x| x.1.clone()).collect() }); - assert!( - !initial_validators_0.is_empty(), - "Empty validator set for session 0 in genesis block!" - ); let initial_validators_1 = T::SessionManager::new_session_genesis(1) .unwrap_or_else(|| initial_validators_0.clone()); - assert!( - !initial_validators_1.is_empty(), - "Empty validator set for session 1 in genesis block!" - ); let queued_keys: Vec<_> = initial_validators_1 - .iter() - .cloned() - .map(|v| { - ( - v.clone(), - Pallet::::load_keys(&v).expect("Validator in session 1 missing keys!"), - ) - }) + .into_iter() + .filter_map(|v| Pallet::::load_keys(&v).map(|k| (v, k))) .collect(); // Tell everyone about the genesis session keys diff --git a/substrate/frame/session/src/migrations/v1.rs b/substrate/frame/session/src/migrations/v1.rs index 394a1f4a5227c2bc3bedf0d177e1f9ee88a89528..b6838099837a00a7f440cf8229fbdc2c9bd5b896 100644 --- a/substrate/frame/session/src/migrations/v1.rs +++ b/substrate/frame/session/src/migrations/v1.rs @@ -15,8 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::str; use sp_io::hashing::twox_128; -use sp_std::str; use frame_support::{ storage::{generator::StorageValue, StoragePrefixedMap}, diff --git a/substrate/frame/session/src/mock.rs b/substrate/frame/session/src/mock.rs index eb9eddfd847e6275363ff297671d4318fefd3283..89804f72cd6268314b28724329229882bf9a2103 100644 --- a/substrate/frame/session/src/mock.rs +++ b/substrate/frame/session/src/mock.rs @@ -24,20 +24,12 @@ use crate::historical as pallet_session_historical; use std::collections::BTreeMap; -use sp_core::{crypto::key_types::DUMMY, H256}; -use sp_runtime::{ - impl_opaque_keys, - testing::UintAuthorityId, - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_core::crypto::key_types::DUMMY; +use sp_runtime::{impl_opaque_keys, testing::UintAuthorityId, BuildStorage}; use sp_staking::SessionIndex; use sp_state_machine::BasicExternalities; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; +use frame_support::{derive_impl, parameter_types, traits::ConstU64}; impl_opaque_keys! { pub struct MockSessionKeys { @@ -234,29 +226,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_timestamp::Config for Test { diff --git a/substrate/frame/society/Cargo.toml b/substrate/frame/society/Cargo.toml index 8b24f637f420012953233832f9701f050fca73ff..3dab082b3954b7bc1f5e20463c6df5192305550b 100644 --- a/substrate/frame/society/Cargo.toml +++ b/substrate/frame/society/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.17", default-features = false } +log = { workspace = true } rand_chacha = { version = "0.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } diff --git a/substrate/frame/society/src/migrations.rs b/substrate/frame/society/src/migrations.rs index a995c9d7be7f2a1c46fee6d222862fcd1f03ae58..dafb1e0b9e5e99706de94b1a4fdd1808343af29d 100644 --- a/substrate/frame/society/src/migrations.rs +++ b/substrate/frame/society/src/migrations.rs @@ -29,7 +29,7 @@ const TARGET: &'static str = "runtime::society::migration"; /// This migration moves all the state to v2 of Society. pub struct VersionUncheckedMigrateToV2, I: 'static, PastPayouts>( - sp_std::marker::PhantomData<(T, I, PastPayouts)>, + core::marker::PhantomData<(T, I, PastPayouts)>, ); impl< diff --git a/substrate/frame/society/src/weights.rs b/substrate/frame/society/src/weights.rs index 7c59aed8449a21c72afb39bdd11e14a315d1c659..c32c2383ac99351ae53b97bdcbea8cd5dfad7744 100644 --- a/substrate/frame/society/src/weights.rs +++ b/substrate/frame/society/src/weights.rs @@ -41,7 +41,7 @@ #![allow(unused_imports)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_society. pub trait WeightInfo { diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index 2c3f50beaeaea9981beed9d4771bc49b11d83901..d2a46146931b8863ae347277db8076850dc76d68 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } @@ -33,7 +33,7 @@ pallet-session = { path = "../session", default-features = false, features = [ pallet-authorship = { path = "../authorship", default-features = false } sp-application-crypto = { path = "../../primitives/application-crypto", default-features = false, features = ["serde"] } frame-election-provider-support = { path = "../election-provider-support", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } # Optional imports for benchmarking frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } diff --git a/substrate/frame/staking/reward-curve/Cargo.toml b/substrate/frame/staking/reward-curve/Cargo.toml index 26ffbd7efbcb57d85cc4f46607167c98d2b585c4..e2a2782db2da1527eff9d9948020fb72bd02e370 100644 --- a/substrate/frame/staking/reward-curve/Cargo.toml +++ b/substrate/frame/staking/reward-curve/Cargo.toml @@ -20,8 +20,8 @@ proc-macro = true [dependencies] proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.48", features = ["full", "visit"] } +quote = { workspace = true } +syn = { features = ["full", "visit"], workspace = true } [dev-dependencies] sp-runtime = { path = "../../../primitives/runtime" } diff --git a/substrate/frame/staking/reward-fn/Cargo.toml b/substrate/frame/staking/reward-fn/Cargo.toml index 0b8903f28718cb917a9742db79380c25d62747c9..5169db5072e2fc80805605c3517d8c6e779e5620 100644 --- a/substrate/frame/staking/reward-fn/Cargo.toml +++ b/substrate/frame/staking/reward-fn/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [lib] [dependencies] -log = { version = "0.4.17", default-features = false } +log = { workspace = true } sp-arithmetic = { path = "../../../primitives/arithmetic", default-features = false } [features] diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 7bcc68cdfe6f51c001959ba4f2fe2d28c9c7f0ed..a83060873973cbaf568fc729535ce514cd90fcd6 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -855,7 +855,8 @@ benchmarks! { ConfigOp::Set(u32::MAX), ConfigOp::Set(u32::MAX), ConfigOp::Set(Percent::max_value()), - ConfigOp::Set(Perbill::max_value()) + ConfigOp::Set(Perbill::max_value()), + ConfigOp::Set(Percent::max_value()) ) verify { assert_eq!(MinNominatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MinValidatorBond::::get(), BalanceOf::::max_value()); @@ -863,6 +864,7 @@ benchmarks! { assert_eq!(MaxValidatorsCount::::get(), Some(u32::MAX)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(100))); assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); + assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(100))); } set_staking_configs_all_remove { @@ -873,6 +875,7 @@ benchmarks! { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, + ConfigOp::Remove, ConfigOp::Remove ) verify { assert!(!MinNominatorBond::::exists()); @@ -881,6 +884,7 @@ benchmarks! { assert!(!MaxValidatorsCount::::exists()); assert!(!ChillThreshold::::exists()); assert!(!MinCommission::::exists()); + assert!(!MaxStakedRewards::::exists()); } chill_other { @@ -904,6 +908,7 @@ benchmarks! { ConfigOp::Set(0), ConfigOp::Set(Percent::from_percent(0)), ConfigOp::Set(Zero::zero()), + ConfigOp::Noop, )?; let caller = whitelisted_caller(); diff --git a/substrate/frame/staking/src/election_size_tracker.rs b/substrate/frame/staking/src/election_size_tracker.rs index 283ae0140ee6894b19dce2f6bb67bbcc771029f0..36e7fa48fda5a48709d85ab5e53fc22eb6a61e89 100644 --- a/substrate/frame/staking/src/election_size_tracker.rs +++ b/substrate/frame/staking/src/election_size_tracker.rs @@ -84,7 +84,7 @@ use frame_election_provider_support::{ pub struct StaticTracker { pub size: usize, pub counter: usize, - _marker: sp_std::marker::PhantomData, + _marker: core::marker::PhantomData, } impl Default for StaticTracker { diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 4487fced28f973e5f26263fa86038439a5822a3e..479f35c061a0cc87c48c69d89a6d37b98e4b43f7 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -202,6 +202,12 @@ //! ```nocompile //! remaining_payout = max_yearly_inflation * total_tokens / era_per_year - staker_payout //! ``` +//! +//! Note, however, that it is possible to set a cap on the total `staker_payout` for the era through +//! the `MaxStakersRewards` storage type. The `era_payout` implementor must ensure that the +//! `max_payout = remaining_payout + (staker_payout * max_stakers_rewards)`. The excess payout that +//! is not allocated for stakers is the era remaining reward. +//! //! The remaining reward is send to the configurable end-point [`Config::RewardRemainder`]. //! //! ### Reward Calculation @@ -897,8 +903,10 @@ impl EraPayout for () { /// Adaptor to turn a `PiecewiseLinear` curve definition into an `EraPayout` impl, used for /// backwards compatibility. pub struct ConvertCurve(sp_std::marker::PhantomData); -impl>> - EraPayout for ConvertCurve +impl EraPayout for ConvertCurve +where + Balance: AtLeast32BitUnsigned + Clone + Copy, + T: Get<&'static PiecewiseLinear<'static>>, { fn era_payout( total_staked: Balance, @@ -912,7 +920,7 @@ impl = StorageValue, Vec<(u32, bool)>, ValueQuery>; - pub struct MigrateToV14(sp_std::marker::PhantomData); + pub struct MigrateToV14(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV14 { fn on_runtime_upgrade() -> Weight { let current = Pallet::::current_storage_version(); @@ -127,7 +127,7 @@ pub mod v14 { pub mod v13 { use super::*; - pub struct MigrateToV13(sp_std::marker::PhantomData); + pub struct MigrateToV13(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV13 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { @@ -183,7 +183,7 @@ pub mod v12 { /// /// We will be depending on the configurable value of `T::HistoryDepth` post /// this release. - pub struct MigrateToV12(sp_std::marker::PhantomData); + pub struct MigrateToV12(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV12 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { @@ -237,7 +237,7 @@ pub mod v11 { #[cfg(feature = "try-runtime")] use sp_io::hashing::twox_128; - pub struct MigrateToV11(sp_std::marker::PhantomData<(T, P, N)>); + pub struct MigrateToV11(core::marker::PhantomData<(T, P, N)>); impl> OnRuntimeUpgrade for MigrateToV11 { @@ -333,7 +333,7 @@ pub mod v10 { /// That means we might slash someone a bit too early, but we will definitely /// won't forget to slash them. The cap of 512 is somewhat randomly taken to /// prevent us from iterating over an arbitrary large number of keys `on_runtime_upgrade`. - pub struct MigrateToV10(sp_std::marker::PhantomData); + pub struct MigrateToV10(core::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV10 { fn on_runtime_upgrade() -> frame_support::weights::Weight { if StorageVersion::::get() == ObsoleteReleases::V9_0_0 { diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index d496cebbeab311f3c8e398712d80442a9854528b..c489fdc394348c6fdfbdef3dea2bed08bb5671ed 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -31,14 +31,8 @@ use frame_support::{ weights::constants::RocksDbWeight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; -use sp_core::H256; use sp_io; -use sp_runtime::{ - curve::PiecewiseLinear, - testing::UintAuthorityId, - traits::{IdentityLookup, Zero}, - BuildStorage, -}; +use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; use sp_staking::{ offence::{OffenceDetails, OnOffenceHandler}, OnStakingUpdate, @@ -49,7 +43,6 @@ pub const BLOCK_TIME: u64 = 1000; /// The AccountId alias in this test module. pub(crate) type AccountId = u64; -pub(crate) type Nonce = u64; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; @@ -127,29 +120,9 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type Nonce = Nonce; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = frame_support::traits::ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; } impl pallet_balances::Config for Test { type MaxLocks = frame_support::traits::ConstU32<1024>; diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index d3cf3333498d33ac72ce5b634ab593e984b8ffbe..1265b531b65f0c742dada5dac1585a3951871b7a 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -36,12 +36,12 @@ use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::historical; use sp_runtime::{ traits::{Bounded, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}, - Perbill, + Perbill, Percent, }; use sp_staking::{ currency_to_vote::CurrencyToVote, offence::{OffenceDetails, OnOffenceHandler}, - EraIndex, Page, SessionIndex, Stake, + EraIndex, OnStakingUpdate, Page, SessionIndex, Stake, StakingAccount::{self, Controller, Stash}, StakingInterface, }; @@ -150,6 +150,9 @@ impl Pallet { // Already checked that this won't overflow by entry condition. let value = old_total.defensive_saturating_sub(new_total); Self::deposit_event(Event::::Withdrawn { stash, amount: value }); + + // notify listeners. + T::EventListeners::on_withdraw(controller, value); } Ok(used_weight) @@ -502,9 +505,18 @@ impl Pallet { .saturated_into::(); let staked = Self::eras_total_stake(&active_era.index); let issuance = T::Currency::total_issuance(); + let (validator_payout, remainder) = T::EraPayout::era_payout(staked, issuance, era_duration); + let total_payout = validator_payout.saturating_add(remainder); + let max_staked_rewards = + MaxStakedRewards::::get().unwrap_or(Percent::from_percent(100)); + + // apply cap to validators payout and add difference to remainder. + let validator_payout = validator_payout.min(max_staked_rewards * total_payout); + let remainder = total_payout.saturating_sub(validator_payout); + Self::deposit_event(Event::::EraPaid { era_index: active_era.index, validator_payout, diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index b14583b891e51a93a2b4a7a64083ea79dd2df92f..35fbee8d3478e8b0e1e05759ba886d42a224625e 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -271,7 +271,7 @@ pub mod pallet { /// Something that listens to staking updates and performs actions based on the data it /// receives. /// - /// WARNING: this only reports slashing events for the time being. + /// WARNING: this only reports slashing and withdraw events for the time being. type EventListeners: sp_staking::OnStakingUpdate>; // `DisablingStragegy` controls how validators are disabled @@ -563,6 +563,12 @@ pub mod pallet { #[pallet::getter(fn force_era)] pub type ForceEra = StorageValue<_, Forcing, ValueQuery>; + /// Maximum staked rewards, i.e. the percentage of the era inflation that + /// is used for stake rewards. + /// See [Era payout](./index.html#era-payout). + #[pallet::storage] + pub type MaxStakedRewards = StorageValue<_, Percent, OptionQuery>; + /// The percentage of the slash that is distributed to reporters. /// /// The rest of the slashed value is handled by the `Slash`. @@ -1714,6 +1720,7 @@ pub mod pallet { max_validator_count: ConfigOp, chill_threshold: ConfigOp, min_commission: ConfigOp, + max_staked_rewards: ConfigOp, ) -> DispatchResult { ensure_root(origin)?; @@ -1733,6 +1740,7 @@ pub mod pallet { config_op_exp!(MaxValidatorsCount, max_validator_count); config_op_exp!(ChillThreshold, chill_threshold); config_op_exp!(MinCommission, min_commission); + config_op_exp!(MaxStakedRewards, max_staked_rewards); Ok(()) } /// Declare a `controller` to stop participating as either a validator or nominator. diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 64f493d90faca1639041a1cda900366958d53a51..ae1e2ab3fdc154c35cab7868e9fbfee862f8e96f 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -55,6 +55,7 @@ fn set_staking_configs_works() { ConfigOp::Set(10), ConfigOp::Set(20), ConfigOp::Set(Percent::from_percent(75)), + ConfigOp::Set(Zero::zero()), ConfigOp::Set(Zero::zero()) )); assert_eq!(MinNominatorBond::::get(), 1_500); @@ -63,6 +64,7 @@ fn set_staking_configs_works() { assert_eq!(MaxValidatorsCount::::get(), Some(20)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(75))); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); + assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(0))); // noop does nothing assert_storage_noop!(assert_ok!(Staking::set_staking_configs( @@ -72,6 +74,7 @@ fn set_staking_configs_works() { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, ConfigOp::Noop ))); @@ -83,6 +86,7 @@ fn set_staking_configs_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, + ConfigOp::Remove, ConfigOp::Remove )); assert_eq!(MinNominatorBond::::get(), 0); @@ -91,6 +95,7 @@ fn set_staking_configs_works() { assert_eq!(MaxValidatorsCount::::get(), None); assert_eq!(ChillThreshold::::get(), None); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); + assert_eq!(MaxStakedRewards::::get(), None); }); } @@ -1748,6 +1753,74 @@ fn rebond_emits_right_value_in_event() { }); } +#[test] +fn max_staked_rewards_default_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(>::get(), None); + + let default_stakers_payout = current_total_payout_for_duration(reward_time_per_era()); + assert!(default_stakers_payout > 0); + start_active_era(1); + + // the final stakers reward is the same as the reward before applied the cap. + assert_eq!(ErasValidatorReward::::get(0).unwrap(), default_stakers_payout); + + // which is the same behaviour if the `MaxStakedRewards` is set to 100%. + >::set(Some(Percent::from_parts(100))); + + let default_stakers_payout = current_total_payout_for_duration(reward_time_per_era()); + assert_eq!(ErasValidatorReward::::get(0).unwrap(), default_stakers_payout); + }) +} + +#[test] +fn max_staked_rewards_works() { + ExtBuilder::default().nominate(true).build_and_execute(|| { + let max_staked_rewards = 10; + + // sets new max staked rewards through set_staking_configs. + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Set(Percent::from_percent(max_staked_rewards)), + )); + + assert_eq!(>::get(), Some(Percent::from_percent(10))); + + // check validators account state. + assert_eq!(Session::validators().len(), 2); + assert!(Session::validators().contains(&11) & Session::validators().contains(&21)); + // balance of the mock treasury account is 0 + assert_eq!(RewardRemainderUnbalanced::get(), 0); + + let max_stakers_payout = current_total_payout_for_duration(reward_time_per_era()); + + start_active_era(1); + + let treasury_payout = RewardRemainderUnbalanced::get(); + let validators_payout = ErasValidatorReward::::get(0).unwrap(); + let total_payout = treasury_payout + validators_payout; + + // max stakers payout (without max staked rewards cap applied) is larger than the final + // validator rewards. The final payment and remainder should be adjusted by redestributing + // the era inflation to apply the cap... + assert!(max_stakers_payout > validators_payout); + + // .. which means that the final validator payout is 10% of the total payout.. + assert_eq!(validators_payout, Percent::from_percent(max_staked_rewards) * total_payout); + // .. and the remainder 90% goes to the treasury. + assert_eq!( + treasury_payout, + Percent::from_percent(100 - max_staked_rewards) * (treasury_payout + validators_payout) + ); + }) +} + #[test] fn reward_to_stake_works() { ExtBuilder::default() @@ -5564,7 +5637,8 @@ fn chill_other_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove + ConfigOp::Remove, + ConfigOp::Noop, )); // Still can't chill these users @@ -5585,7 +5659,8 @@ fn chill_other_works() { ConfigOp::Set(10), ConfigOp::Set(10), ConfigOp::Noop, - ConfigOp::Noop + ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -5606,7 +5681,8 @@ fn chill_other_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Noop, - ConfigOp::Noop + ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -5627,7 +5703,8 @@ fn chill_other_works() { ConfigOp::Set(10), ConfigOp::Set(10), ConfigOp::Set(Percent::from_percent(75)), - ConfigOp::Noop + ConfigOp::Noop, + ConfigOp::Noop, )); // 16 people total because tests start with 2 active one @@ -5673,6 +5750,7 @@ fn capped_stakers_works() { ConfigOp::Set(max), ConfigOp::Remove, ConfigOp::Remove, + ConfigOp::Noop, )); // can create `max - validator_count` validators @@ -5743,6 +5821,7 @@ fn capped_stakers_works() { ConfigOp::Remove, ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, )); assert_ok!(Staking::nominate(RuntimeOrigin::signed(last_nominator), vec![1])); assert_ok!(Staking::validate( @@ -5778,6 +5857,7 @@ fn min_commission_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Set(Perbill::from_percent(10)), + ConfigOp::Noop, )); // can't make it less than 10 now diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs index 7c9a050016406a5ae5b8f98bcd98b0093858628a..6f729e08ba5c839997cd81239f0e4bd39728df14 100644 --- a/substrate/frame/staking/src/weights.rs +++ b/substrate/frame/staking/src/weights.rs @@ -18,9 +18,9 @@ //! Autogenerated weights for `pallet_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-01-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-itmxxexx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-q7z7ruxr-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -99,8 +99,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `927` // Estimated: `4764` - // Minimum execution time: 42_491_000 picoseconds. - Weight::from_parts(44_026_000, 4764) + // Minimum execution time: 42_042_000 picoseconds. + Weight::from_parts(43_292_000, 4764) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -120,8 +120,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1990` // Estimated: `8877` - // Minimum execution time: 88_756_000 picoseconds. - Weight::from_parts(91_000_000, 8877) + // Minimum execution time: 85_050_000 picoseconds. + Weight::from_parts(87_567_000, 8877) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -147,8 +147,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2195` // Estimated: `8877` - // Minimum execution time: 91_331_000 picoseconds. - Weight::from_parts(94_781_000, 8877) + // Minimum execution time: 89_076_000 picoseconds. + Weight::from_parts(92_715_000, 8877) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -167,10 +167,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1115` // Estimated: `4764` - // Minimum execution time: 42_495_000 picoseconds. - Weight::from_parts(44_189_470, 4764) - // Standard Error: 1_389 - .saturating_add(Weight::from_parts(47_484, 0).saturating_mul(s.into())) + // Minimum execution time: 42_067_000 picoseconds. + Weight::from_parts(43_239_807, 4764) + // Standard Error: 831 + .saturating_add(Weight::from_parts(46_257, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -207,10 +207,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 89_004_000 picoseconds. - Weight::from_parts(96_677_570, 6248) - // Standard Error: 4_635 - .saturating_add(Weight::from_parts(1_387_718, 0).saturating_mul(s.into())) + // Minimum execution time: 86_490_000 picoseconds. + Weight::from_parts(95_358_751, 6248) + // Standard Error: 3_952 + .saturating_add(Weight::from_parts(1_294_907, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(11_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -242,8 +242,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1372` // Estimated: `4556` - // Minimum execution time: 51_532_000 picoseconds. - Weight::from_parts(53_308_000, 4556) + // Minimum execution time: 50_326_000 picoseconds. + Weight::from_parts(52_253_000, 4556) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -256,10 +256,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1280 + k * (569 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 28_955_000 picoseconds. - Weight::from_parts(29_609_869, 4556) - // Standard Error: 6_793 - .saturating_add(Weight::from_parts(6_412_124, 0).saturating_mul(k.into())) + // Minimum execution time: 29_305_000 picoseconds. + Weight::from_parts(32_199_604, 4556) + // Standard Error: 7_150 + .saturating_add(Weight::from_parts(6_437_124, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -292,10 +292,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1866 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 64_080_000 picoseconds. - Weight::from_parts(61_985_382, 6248) - // Standard Error: 13_320 - .saturating_add(Weight::from_parts(4_030_513, 0).saturating_mul(n.into())) + // Minimum execution time: 63_267_000 picoseconds. + Weight::from_parts(61_741_404, 6248) + // Standard Error: 12_955 + .saturating_add(Weight::from_parts(3_811_743, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6_u64)) @@ -319,8 +319,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `6248` - // Minimum execution time: 54_194_000 picoseconds. - Weight::from_parts(55_578_000, 6248) + // Minimum execution time: 52_862_000 picoseconds. + Weight::from_parts(54_108_000, 6248) .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -334,8 +334,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 16_597_000 picoseconds. - Weight::from_parts(16_980_000, 4556) + // Minimum execution time: 16_350_000 picoseconds. + Weight::from_parts(16_802_000, 4556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -349,8 +349,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `969` // Estimated: `4556` - // Minimum execution time: 20_626_000 picoseconds. - Weight::from_parts(21_242_000, 4556) + // Minimum execution time: 19_981_000 picoseconds. + Weight::from_parts(20_539_000, 4556) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -362,8 +362,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 19_972_000 picoseconds. - Weight::from_parts(20_470_000, 4556) + // Minimum execution time: 19_304_000 picoseconds. + Weight::from_parts(20_000_000, 4556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -373,8 +373,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_571_000 picoseconds. - Weight::from_parts(2_720_000, 0) + // Minimum execution time: 2_568_000 picoseconds. + Weight::from_parts(2_708_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -383,8 +383,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_056_000 picoseconds. - Weight::from_parts(8_413_000, 0) + // Minimum execution time: 7_950_000 picoseconds. + Weight::from_parts(8_348_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -393,8 +393,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_162_000 picoseconds. - Weight::from_parts(8_497_000, 0) + // Minimum execution time: 7_967_000 picoseconds. + Weight::from_parts(8_222_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -403,8 +403,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_320_000 picoseconds. - Weight::from_parts(8_564_000, 0) + // Minimum execution time: 8_006_000 picoseconds. + Weight::from_parts(8_440_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -414,10 +414,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_470_000 picoseconds. - Weight::from_parts(3_110_242, 0) - // Standard Error: 63 - .saturating_add(Weight::from_parts(11_786, 0).saturating_mul(v.into())) + // Minimum execution time: 2_524_000 picoseconds. + Weight::from_parts(3_123_608, 0) + // Standard Error: 59 + .saturating_add(Weight::from_parts(11_596, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:5900 w:11800) @@ -431,10 +431,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1356 + i * (151 ±0)` // Estimated: `990 + i * (3566 ±0)` - // Minimum execution time: 2_101_000 picoseconds. - Weight::from_parts(2_238_000, 990) - // Standard Error: 56_753 - .saturating_add(Weight::from_parts(18_404_902, 0).saturating_mul(i.into())) + // Minimum execution time: 2_092_000 picoseconds. + Weight::from_parts(2_258_000, 990) + // Standard Error: 32_695 + .saturating_add(Weight::from_parts(16_669_219, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(i.into())) @@ -472,10 +472,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 86_765_000 picoseconds. - Weight::from_parts(95_173_565, 6248) - // Standard Error: 4_596 - .saturating_add(Weight::from_parts(1_354_849, 0).saturating_mul(s.into())) + // Minimum execution time: 84_275_000 picoseconds. + Weight::from_parts(92_512_416, 6248) + // Standard Error: 3_633 + .saturating_add(Weight::from_parts(1_315_923, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(12_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -488,10 +488,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `66672` // Estimated: `70137` - // Minimum execution time: 104_490_000 picoseconds. - Weight::from_parts(1_162_956_951, 70137) - // Standard Error: 76_760 - .saturating_add(Weight::from_parts(6_485_569, 0).saturating_mul(s.into())) + // Minimum execution time: 101_707_000 picoseconds. + Weight::from_parts(912_819_462, 70137) + // Standard Error: 57_547 + .saturating_add(Weight::from_parts(4_856_799, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -528,10 +528,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `33297 + n * (377 ±0)` // Estimated: `30944 + n * (3774 ±0)` - // Minimum execution time: 144_790_000 picoseconds. - Weight::from_parts(36_764_791, 30944) - // Standard Error: 89_592 - .saturating_add(Weight::from_parts(49_620_105, 0).saturating_mul(n.into())) + // Minimum execution time: 138_657_000 picoseconds. + Weight::from_parts(167_173_445, 30944) + // Standard Error: 25_130 + .saturating_add(Weight::from_parts(44_566_012, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -555,10 +555,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1991 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 81_768_000 picoseconds. - Weight::from_parts(85_332_982, 8877) - // Standard Error: 5_380 - .saturating_add(Weight::from_parts(70_298, 0).saturating_mul(l.into())) + // Minimum execution time: 80_061_000 picoseconds. + Weight::from_parts(82_836_434, 8877) + // Standard Error: 4_348 + .saturating_add(Weight::from_parts(75_744, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -593,10 +593,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 96_123_000 picoseconds. - Weight::from_parts(100_278_672, 6248) - // Standard Error: 3_487 - .saturating_add(Weight::from_parts(1_326_503, 0).saturating_mul(s.into())) + // Minimum execution time: 92_560_000 picoseconds. + Weight::from_parts(97_684_741, 6248) + // Standard Error: 3_361 + .saturating_add(Weight::from_parts(1_292_732, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(11_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -642,12 +642,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 572_893_000 picoseconds. - Weight::from_parts(578_010_000, 512390) - // Standard Error: 2_094_268 - .saturating_add(Weight::from_parts(68_419_710, 0).saturating_mul(v.into())) - // Standard Error: 208_682 - .saturating_add(Weight::from_parts(18_826_175, 0).saturating_mul(n.into())) + // Minimum execution time: 564_963_000 picoseconds. + Weight::from_parts(569_206_000, 512390) + // Standard Error: 2_033_235 + .saturating_add(Weight::from_parts(68_025_841, 0).saturating_mul(v.into())) + // Standard Error: 202_600 + .saturating_add(Weight::from_parts(17_916_770, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(206_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -678,12 +678,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3175 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 33_836_205_000 picoseconds. - Weight::from_parts(34_210_443_000, 512390) - // Standard Error: 441_692 - .saturating_add(Weight::from_parts(6_122_533, 0).saturating_mul(v.into())) - // Standard Error: 441_692 - .saturating_add(Weight::from_parts(4_418_264, 0).saturating_mul(n.into())) + // Minimum execution time: 32_196_540_000 picoseconds. + Weight::from_parts(32_341_871_000, 512390) + // Standard Error: 354_657 + .saturating_add(Weight::from_parts(5_143_440, 0).saturating_mul(v.into())) + // Standard Error: 354_657 + .saturating_add(Weight::from_parts(3_328_189, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(201_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -700,10 +700,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `979 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_454_689_000 picoseconds. - Weight::from_parts(161_771_064, 3510) - // Standard Error: 31_022 - .saturating_add(Weight::from_parts(4_820_158, 0).saturating_mul(v.into())) + // Minimum execution time: 2_381_903_000 picoseconds. + Weight::from_parts(32_693_059, 3510) + // Standard Error: 10_000 + .saturating_add(Weight::from_parts(4_736_173, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -724,8 +724,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_073_000 picoseconds. - Weight::from_parts(5_452_000, 0) + // Minimum execution time: 5_434_000 picoseconds. + Weight::from_parts(5_742_000, 0) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -744,8 +744,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_465_000 picoseconds. - Weight::from_parts(4_832_000, 0) + // Minimum execution time: 4_588_000 picoseconds. + Weight::from_parts(4_854_000, 0) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -774,8 +774,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1939` // Estimated: `6248` - // Minimum execution time: 71_239_000 picoseconds. - Weight::from_parts(74_649_000, 6248) + // Minimum execution time: 68_780_000 picoseconds. + Weight::from_parts(71_479_000, 6248) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -787,8 +787,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `691` // Estimated: `3510` - // Minimum execution time: 12_525_000 picoseconds. - Weight::from_parts(13_126_000, 3510) + // Minimum execution time: 12_268_000 picoseconds. + Weight::from_parts(12_661_000, 3510) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -798,8 +798,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_918_000 picoseconds. - Weight::from_parts(3_176_000, 0) + // Minimum execution time: 3_071_000 picoseconds. + Weight::from_parts(3_334_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } } @@ -820,8 +820,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `927` // Estimated: `4764` - // Minimum execution time: 42_491_000 picoseconds. - Weight::from_parts(44_026_000, 4764) + // Minimum execution time: 42_042_000 picoseconds. + Weight::from_parts(43_292_000, 4764) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -841,8 +841,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1990` // Estimated: `8877` - // Minimum execution time: 88_756_000 picoseconds. - Weight::from_parts(91_000_000, 8877) + // Minimum execution time: 85_050_000 picoseconds. + Weight::from_parts(87_567_000, 8877) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -868,8 +868,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2195` // Estimated: `8877` - // Minimum execution time: 91_331_000 picoseconds. - Weight::from_parts(94_781_000, 8877) + // Minimum execution time: 89_076_000 picoseconds. + Weight::from_parts(92_715_000, 8877) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -888,10 +888,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1115` // Estimated: `4764` - // Minimum execution time: 42_495_000 picoseconds. - Weight::from_parts(44_189_470, 4764) - // Standard Error: 1_389 - .saturating_add(Weight::from_parts(47_484, 0).saturating_mul(s.into())) + // Minimum execution time: 42_067_000 picoseconds. + Weight::from_parts(43_239_807, 4764) + // Standard Error: 831 + .saturating_add(Weight::from_parts(46_257, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -928,10 +928,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 89_004_000 picoseconds. - Weight::from_parts(96_677_570, 6248) - // Standard Error: 4_635 - .saturating_add(Weight::from_parts(1_387_718, 0).saturating_mul(s.into())) + // Minimum execution time: 86_490_000 picoseconds. + Weight::from_parts(95_358_751, 6248) + // Standard Error: 3_952 + .saturating_add(Weight::from_parts(1_294_907, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(11_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -963,8 +963,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1372` // Estimated: `4556` - // Minimum execution time: 51_532_000 picoseconds. - Weight::from_parts(53_308_000, 4556) + // Minimum execution time: 50_326_000 picoseconds. + Weight::from_parts(52_253_000, 4556) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -977,10 +977,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1280 + k * (569 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 28_955_000 picoseconds. - Weight::from_parts(29_609_869, 4556) - // Standard Error: 6_793 - .saturating_add(Weight::from_parts(6_412_124, 0).saturating_mul(k.into())) + // Minimum execution time: 29_305_000 picoseconds. + Weight::from_parts(32_199_604, 4556) + // Standard Error: 7_150 + .saturating_add(Weight::from_parts(6_437_124, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -1013,10 +1013,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1866 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 64_080_000 picoseconds. - Weight::from_parts(61_985_382, 6248) - // Standard Error: 13_320 - .saturating_add(Weight::from_parts(4_030_513, 0).saturating_mul(n.into())) + // Minimum execution time: 63_267_000 picoseconds. + Weight::from_parts(61_741_404, 6248) + // Standard Error: 12_955 + .saturating_add(Weight::from_parts(3_811_743, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(6_u64)) @@ -1040,8 +1040,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1650` // Estimated: `6248` - // Minimum execution time: 54_194_000 picoseconds. - Weight::from_parts(55_578_000, 6248) + // Minimum execution time: 52_862_000 picoseconds. + Weight::from_parts(54_108_000, 6248) .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1055,8 +1055,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 16_597_000 picoseconds. - Weight::from_parts(16_980_000, 4556) + // Minimum execution time: 16_350_000 picoseconds. + Weight::from_parts(16_802_000, 4556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1070,8 +1070,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `969` // Estimated: `4556` - // Minimum execution time: 20_626_000 picoseconds. - Weight::from_parts(21_242_000, 4556) + // Minimum execution time: 19_981_000 picoseconds. + Weight::from_parts(20_539_000, 4556) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1083,8 +1083,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `902` // Estimated: `4556` - // Minimum execution time: 19_972_000 picoseconds. - Weight::from_parts(20_470_000, 4556) + // Minimum execution time: 19_304_000 picoseconds. + Weight::from_parts(20_000_000, 4556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1094,8 +1094,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_571_000 picoseconds. - Weight::from_parts(2_720_000, 0) + // Minimum execution time: 2_568_000 picoseconds. + Weight::from_parts(2_708_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1104,8 +1104,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_056_000 picoseconds. - Weight::from_parts(8_413_000, 0) + // Minimum execution time: 7_950_000 picoseconds. + Weight::from_parts(8_348_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1114,8 +1114,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_162_000 picoseconds. - Weight::from_parts(8_497_000, 0) + // Minimum execution time: 7_967_000 picoseconds. + Weight::from_parts(8_222_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1124,8 +1124,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_320_000 picoseconds. - Weight::from_parts(8_564_000, 0) + // Minimum execution time: 8_006_000 picoseconds. + Weight::from_parts(8_440_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -1135,10 +1135,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_470_000 picoseconds. - Weight::from_parts(3_110_242, 0) - // Standard Error: 63 - .saturating_add(Weight::from_parts(11_786, 0).saturating_mul(v.into())) + // Minimum execution time: 2_524_000 picoseconds. + Weight::from_parts(3_123_608, 0) + // Standard Error: 59 + .saturating_add(Weight::from_parts(11_596, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:5900 w:11800) @@ -1152,10 +1152,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1356 + i * (151 ±0)` // Estimated: `990 + i * (3566 ±0)` - // Minimum execution time: 2_101_000 picoseconds. - Weight::from_parts(2_238_000, 990) - // Standard Error: 56_753 - .saturating_add(Weight::from_parts(18_404_902, 0).saturating_mul(i.into())) + // Minimum execution time: 2_092_000 picoseconds. + Weight::from_parts(2_258_000, 990) + // Standard Error: 32_695 + .saturating_add(Weight::from_parts(16_669_219, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 3566).saturating_mul(i.into())) @@ -1193,10 +1193,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 86_765_000 picoseconds. - Weight::from_parts(95_173_565, 6248) - // Standard Error: 4_596 - .saturating_add(Weight::from_parts(1_354_849, 0).saturating_mul(s.into())) + // Minimum execution time: 84_275_000 picoseconds. + Weight::from_parts(92_512_416, 6248) + // Standard Error: 3_633 + .saturating_add(Weight::from_parts(1_315_923, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(12_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -1209,10 +1209,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `66672` // Estimated: `70137` - // Minimum execution time: 104_490_000 picoseconds. - Weight::from_parts(1_162_956_951, 70137) - // Standard Error: 76_760 - .saturating_add(Weight::from_parts(6_485_569, 0).saturating_mul(s.into())) + // Minimum execution time: 101_707_000 picoseconds. + Weight::from_parts(912_819_462, 70137) + // Standard Error: 57_547 + .saturating_add(Weight::from_parts(4_856_799, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1249,10 +1249,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `33297 + n * (377 ±0)` // Estimated: `30944 + n * (3774 ±0)` - // Minimum execution time: 144_790_000 picoseconds. - Weight::from_parts(36_764_791, 30944) - // Standard Error: 89_592 - .saturating_add(Weight::from_parts(49_620_105, 0).saturating_mul(n.into())) + // Minimum execution time: 138_657_000 picoseconds. + Weight::from_parts(167_173_445, 30944) + // Standard Error: 25_130 + .saturating_add(Weight::from_parts(44_566_012, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1276,10 +1276,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1991 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 81_768_000 picoseconds. - Weight::from_parts(85_332_982, 8877) - // Standard Error: 5_380 - .saturating_add(Weight::from_parts(70_298, 0).saturating_mul(l.into())) + // Minimum execution time: 80_061_000 picoseconds. + Weight::from_parts(82_836_434, 8877) + // Standard Error: 4_348 + .saturating_add(Weight::from_parts(75_744, 0).saturating_mul(l.into())) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -1314,10 +1314,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `2196 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 96_123_000 picoseconds. - Weight::from_parts(100_278_672, 6248) - // Standard Error: 3_487 - .saturating_add(Weight::from_parts(1_326_503, 0).saturating_mul(s.into())) + // Minimum execution time: 92_560_000 picoseconds. + Weight::from_parts(97_684_741, 6248) + // Standard Error: 3_361 + .saturating_add(Weight::from_parts(1_292_732, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(11_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) @@ -1363,12 +1363,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 572_893_000 picoseconds. - Weight::from_parts(578_010_000, 512390) - // Standard Error: 2_094_268 - .saturating_add(Weight::from_parts(68_419_710, 0).saturating_mul(v.into())) - // Standard Error: 208_682 - .saturating_add(Weight::from_parts(18_826_175, 0).saturating_mul(n.into())) + // Minimum execution time: 564_963_000 picoseconds. + Weight::from_parts(569_206_000, 512390) + // Standard Error: 2_033_235 + .saturating_add(Weight::from_parts(68_025_841, 0).saturating_mul(v.into())) + // Standard Error: 202_600 + .saturating_add(Weight::from_parts(17_916_770, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(206_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1399,12 +1399,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3175 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 33_836_205_000 picoseconds. - Weight::from_parts(34_210_443_000, 512390) - // Standard Error: 441_692 - .saturating_add(Weight::from_parts(6_122_533, 0).saturating_mul(v.into())) - // Standard Error: 441_692 - .saturating_add(Weight::from_parts(4_418_264, 0).saturating_mul(n.into())) + // Minimum execution time: 32_196_540_000 picoseconds. + Weight::from_parts(32_341_871_000, 512390) + // Standard Error: 354_657 + .saturating_add(Weight::from_parts(5_143_440, 0).saturating_mul(v.into())) + // Standard Error: 354_657 + .saturating_add(Weight::from_parts(3_328_189, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(201_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1421,10 +1421,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `979 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_454_689_000 picoseconds. - Weight::from_parts(161_771_064, 3510) - // Standard Error: 31_022 - .saturating_add(Weight::from_parts(4_820_158, 0).saturating_mul(v.into())) + // Minimum execution time: 2_381_903_000 picoseconds. + Weight::from_parts(32_693_059, 3510) + // Standard Error: 10_000 + .saturating_add(Weight::from_parts(4_736_173, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -1445,8 +1445,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_073_000 picoseconds. - Weight::from_parts(5_452_000, 0) + // Minimum execution time: 5_434_000 picoseconds. + Weight::from_parts(5_742_000, 0) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -1465,8 +1465,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_465_000 picoseconds. - Weight::from_parts(4_832_000, 0) + // Minimum execution time: 4_588_000 picoseconds. + Weight::from_parts(4_854_000, 0) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -1495,8 +1495,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1939` // Estimated: `6248` - // Minimum execution time: 71_239_000 picoseconds. - Weight::from_parts(74_649_000, 6248) + // Minimum execution time: 68_780_000 picoseconds. + Weight::from_parts(71_479_000, 6248) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1508,8 +1508,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `691` // Estimated: `3510` - // Minimum execution time: 12_525_000 picoseconds. - Weight::from_parts(13_126_000, 3510) + // Minimum execution time: 12_268_000 picoseconds. + Weight::from_parts(12_661_000, 3510) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1519,8 +1519,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_918_000 picoseconds. - Weight::from_parts(3_176_000, 0) + // Minimum execution time: 3_071_000 picoseconds. + Weight::from_parts(3_334_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/substrate/frame/state-trie-migration/Cargo.toml b/substrate/frame/state-trie-migration/Cargo.toml index 1fb49e6256205ccc5aff3514dfa0a16ee8713265..e837956613edd2a30146bc6e41b4518d55c95095 100644 --- a/substrate/frame/state-trie-migration/Cargo.toml +++ b/substrate/frame/state-trie-migration/Cargo.toml @@ -16,9 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } thousands = { version = "0.2.0", optional = true } zstd = { version = "0.12.4", default-features = false, optional = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } diff --git a/substrate/frame/statement/Cargo.toml b/substrate/frame/statement/Cargo.toml index 58f8b40dc26d0ee45ebec7b9ab018b4f9b0debd3..6827dbda962b3d4973133f38b723579ab1ddfd4d 100644 --- a/substrate/frame/statement/Cargo.toml +++ b/substrate/frame/statement/Cargo.toml @@ -25,7 +25,7 @@ sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } sp-io = { path = "../../primitives/io", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/substrate/frame/statement/src/mock.rs b/substrate/frame/statement/src/mock.rs index c5bee2639dcd704acdbec7e6dd6bae1c922e67d2..4ab9cf9e0f96a33fe70de6c019234b9a3829eb7e 100644 --- a/substrate/frame/statement/src/mock.rs +++ b/substrate/frame/statement/src/mock.rs @@ -22,14 +22,10 @@ use super::*; use crate as pallet_statement; use frame_support::{ derive_impl, ord_parameter_types, - traits::{ConstU32, ConstU64, Everything}, - weights::constants::RocksDbWeight, -}; -use sp_core::{Pair, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - AccountId32, BuildStorage, + traits::{ConstU32, ConstU64}, }; +use sp_core::Pair; +use sp_runtime::{traits::IdentityLookup, AccountId32, BuildStorage}; type Block = frame_system::mocking::MockBlock; @@ -49,29 +45,10 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = RocksDbWeight; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = AccountId32; type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/sudo/src/extension.rs b/substrate/frame/sudo/src/extension.rs index c717ff3567268ae7bf868120d6411d1abe63c712..e90286e5a7c6bae73794bae7efd5fda4a496bb25 100644 --- a/substrate/frame/sudo/src/extension.rs +++ b/substrate/frame/sudo/src/extension.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Config, Pallet}; +use crate::{Config, Key}; use codec::{Decode, Encode}; use frame_support::{dispatch::DispatchInfo, ensure}; use scale_info::TypeInfo; @@ -86,7 +86,7 @@ where info: &DispatchInfoOf, _len: usize, ) -> TransactionValidity { - let sudo_key: T::AccountId = >::key().ok_or(UnknownTransaction::CannotLookup)?; + let sudo_key: T::AccountId = Key::::get().ok_or(UnknownTransaction::CannotLookup)?; ensure!(*who == sudo_key, InvalidTransaction::BadSigner); Ok(ValidTransaction { diff --git a/substrate/frame/sudo/src/lib.rs b/substrate/frame/sudo/src/lib.rs index 4f14c32ff76b0e0038597c9d715d632985eceeea..2ebe4cb015712dbf8df630a7114e6fc32c247e0d 100644 --- a/substrate/frame/sudo/src/lib.rs +++ b/substrate/frame/sudo/src/lib.rs @@ -329,7 +329,6 @@ pub mod pallet { /// The `AccountId` of the sudo key. #[pallet::storage] - #[pallet::getter(fn key)] pub(super) type Key = StorageValue<_, T::AccountId, OptionQuery>; #[pallet::genesis_config] @@ -352,7 +351,7 @@ pub mod pallet { let sender = ensure_signed_or_root(origin)?; if let Some(sender) = sender { - if Self::key().map_or(false, |k| k == sender) { + if Key::::get().map_or(false, |k| k == sender) { Ok(()) } else { Err(Error::::RequireSudo.into()) diff --git a/substrate/frame/sudo/src/mock.rs b/substrate/frame/sudo/src/mock.rs index 9ad14804524a9400fa5e876aa9119d44ee45857c..3b907a27168366c4b4b56ceabde60d78ff9e42fd 100644 --- a/substrate/frame/sudo/src/mock.rs +++ b/substrate/frame/sudo/src/mock.rs @@ -19,16 +19,9 @@ use super::*; use crate as sudo; -use frame_support::{ - derive_impl, - traits::{ConstU32, Contains}, -}; -use sp_core::{ConstU64, H256}; +use frame_support::{derive_impl, traits::Contains}; use sp_io; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; // Logger module to track execution. #[frame_support::pallet] @@ -113,29 +106,7 @@ impl Contains for BlockEverything { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = BlockEverything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } // Implement the logger module's `Config` on the Test runtime. diff --git a/substrate/frame/sudo/src/tests.rs b/substrate/frame/sudo/src/tests.rs index 73689415a737fd3e2acba663099f9042f88ace4c..00bb86cc2686f2410d09077d9b388750914eb74c 100644 --- a/substrate/frame/sudo/src/tests.rs +++ b/substrate/frame/sudo/src/tests.rs @@ -28,7 +28,7 @@ use mock::{ fn test_setup_works() { // Environment setup, logger storage, and sudo `key` retrieval should work as expected. new_test_ext(1).execute_with(|| { - assert_eq!(Sudo::key(), Some(1u64)); + assert_eq!(Key::::get(), Some(1u64)); assert!(Logger::i32_log().is_empty()); assert!(Logger::account_log().is_empty()); }); @@ -135,7 +135,7 @@ fn set_key_basics() { new_test_ext(1).execute_with(|| { // A root `key` can change the root `key` assert_ok!(Sudo::set_key(RuntimeOrigin::signed(1), 2)); - assert_eq!(Sudo::key(), Some(2u64)); + assert_eq!(Key::::get(), Some(2u64)); }); new_test_ext(1).execute_with(|| { @@ -161,7 +161,7 @@ fn set_key_emits_events_correctly() { fn remove_key_works() { new_test_ext(1).execute_with(|| { assert_ok!(Sudo::remove_key(RuntimeOrigin::signed(1))); - assert!(Sudo::key().is_none()); + assert!(Key::::get().is_none()); System::assert_has_event(TestEvent::Sudo(Event::KeyRemoved {})); assert_noop!(Sudo::remove_key(RuntimeOrigin::signed(1)), Error::::RequireSudo); @@ -173,11 +173,11 @@ fn remove_key_works() { fn using_root_origin_works() { new_test_ext(1).execute_with(|| { assert_ok!(Sudo::remove_key(RuntimeOrigin::root())); - assert!(Sudo::key().is_none()); + assert!(Key::::get().is_none()); System::assert_has_event(TestEvent::Sudo(Event::KeyRemoved {})); assert_ok!(Sudo::set_key(RuntimeOrigin::root(), 1)); - assert_eq!(Some(1), Sudo::key()); + assert_eq!(Some(1), Key::::get()); }); } diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index ad97ad5146e7698dfe0dbac8331e089d093abc6f..72e2d0bfa56938f1353a196f2fbfc95a0e68b48d 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { version = "6.1", default-features = false } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-metadata = { version = "16.0.0", default-features = false, features = ["current"] } @@ -41,12 +41,12 @@ sp-state-machine = { path = "../../primitives/state-machine", default-features = bitflags = "1.3" impl-trait-for-tuples = "0.2.2" smallvec = "1.11.0" -log = { version = "0.4.17", default-features = false } +log = { workspace = true } sp-crypto-hashing-proc-macro = { path = "../../primitives/crypto/hashing/proc-macro" } k256 = { version = "0.13.1", default-features = false, features = ["ecdsa"] } environmental = { version = "1.1.4", default-features = false } sp-genesis-builder = { path = "../../primitives/genesis-builder", default-features = false } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } +serde_json = { features = ["alloc"], workspace = true } docify = "0.2.7" static_assertions = "1.1.0" diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index d77f6595db3360fa4f60362cd9bff49feb9727e7..859475038020aba9afa0a9aa110c51d699cc5358 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -23,8 +23,8 @@ Inflector = "0.11.4" cfg-expr = "0.15.5" itertools = "0.10.3" proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.48", features = ["full"] } +quote = { workspace = true } +syn = { features = ["full", "visit-mut"], workspace = true } frame-support-procedural-tools = { path = "tools" } macro_magic = { version = "0.5.0", features = ["proc_support"] } proc-macro-warning = { version = "1.0.0", default-features = false } diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs index ce2aa0942794d40cc3bfe0f4d65fdd80f8140d52..b0041ccc07541966863e51d40f4b6e26e50a396f 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -178,7 +178,7 @@ pub fn expand_outer_dispatch( type PostInfo = #scrate::dispatch::PostDispatchInfo; fn dispatch(self, origin: RuntimeOrigin) -> #scrate::dispatch::DispatchResultWithPostInfo { if !::filter_call(&origin, &self) { - return #scrate::__private::sp_std::result::Result::Err( + return ::core::result::Result::Err( #system_path::Error::<#runtime>::CallFiltered.into() ); } diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs index ffe55bceb80ef96bc9e7814cfbd8bec6c62ab562..5613047359a72ca828514ef6e492fadc87611128 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/config.rs @@ -99,6 +99,17 @@ pub fn expand_outer_config( ::on_genesis(); } } + + /// Test the `Default` derive impl of the `RuntimeGenesisConfig`. + #[cfg(test)] + #[test] + fn test_genesis_config_builds() { + #scrate::__private::sp_io::TestExternalities::default().execute_with(|| { + ::build( + &RuntimeGenesisConfig::default() + ); + }); + } } } diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs index df69c19a4b617bbe7194bb5958848fc04d8d546a..80b242ccbe493607a59e672b7f0958fc9d0a213c 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs @@ -134,7 +134,6 @@ pub fn expand_outer_enum( enum_ty, )); enum_conversions.extend(expand_enum_conversion( - scrate, pallet_decl, &pallet_enum, &enum_name_ident, @@ -220,7 +219,6 @@ fn expand_enum_variant( } fn expand_enum_conversion( - scrate: &TokenStream, pallet: &Pallet, pallet_enum: &TokenStream, enum_name_ident: &Ident, @@ -247,7 +245,7 @@ fn expand_enum_conversion( impl TryInto<#pallet_enum> for #enum_name_ident { type Error = (); - fn try_into(self) -> #scrate::__private::sp_std::result::Result<#pallet_enum, Self::Error> { + fn try_into(self) -> ::core::result::Result<#pallet_enum, Self::Error> { match self { Self::#variant_name(evt) => Ok(evt), _ => Err(()), diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs index 54ed15f7b1d36d6ee5eb724381152af03bef60c8..6e95fdf116a6fefe92e3fa94f91f4424013aa35f 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs @@ -233,25 +233,38 @@ pub fn construct_runtime(input: TokenStream) -> TokenStream { let input_copy = input.clone(); let definition = syn::parse_macro_input!(input as RuntimeDeclaration); - let res = match definition { - RuntimeDeclaration::Implicit(implicit_def) => - check_pallet_number(input_copy.clone().into(), implicit_def.pallets.len()).and_then( - |_| construct_runtime_implicit_to_explicit(input_copy.into(), implicit_def), - ), - RuntimeDeclaration::Explicit(explicit_decl) => check_pallet_number( - input_copy.clone().into(), - explicit_decl.pallets.len(), - ) - .and_then(|_| { - construct_runtime_explicit_to_explicit_expanded(input_copy.into(), explicit_decl) - }), - RuntimeDeclaration::ExplicitExpanded(explicit_decl) => - check_pallet_number(input_copy.into(), explicit_decl.pallets.len()) - .and_then(|_| construct_runtime_final_expansion(explicit_decl)), + let (check_pallet_number_res, res) = match definition { + RuntimeDeclaration::Implicit(implicit_def) => ( + check_pallet_number(input_copy.clone().into(), implicit_def.pallets.len()), + construct_runtime_implicit_to_explicit(input_copy.into(), implicit_def), + ), + RuntimeDeclaration::Explicit(explicit_decl) => ( + check_pallet_number(input_copy.clone().into(), explicit_decl.pallets.len()), + construct_runtime_explicit_to_explicit_expanded(input_copy.into(), explicit_decl), + ), + RuntimeDeclaration::ExplicitExpanded(explicit_decl) => ( + check_pallet_number(input_copy.into(), explicit_decl.pallets.len()), + construct_runtime_final_expansion(explicit_decl), + ), }; let res = res.unwrap_or_else(|e| e.to_compile_error()); + // We want to provide better error messages to the user and thus, handle the error here + // separately. If there is an error, we print the error and still generate all of the code to + // get in overall less errors for the user. + let res = if let Err(error) = check_pallet_number_res { + let error = error.to_compile_error(); + + quote! { + #error + + #res + } + } else { + res + }; + let res = expander::Expander::new("construct_runtime") .dry(std::env::var("EXPAND_MACROS").is_err()) .verbose(true) diff --git a/substrate/frame/support/procedural/src/dynamic_params.rs b/substrate/frame/support/procedural/src/dynamic_params.rs new file mode 100644 index 0000000000000000000000000000000000000000..b718ccbc9558480587a1f50f4e85a992e5c750da --- /dev/null +++ b/substrate/frame/support/procedural/src/dynamic_params.rs @@ -0,0 +1,563 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Code for the `#[dynamic_params]`, `#[dynamic_pallet_params]` and +//! `#[dynamic_aggregated_params_internal]` macros. + +use frame_support_procedural_tools::generate_access_from_frame_or_crate; +use inflector::Inflector; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use syn::{parse2, spanned::Spanned, visit_mut, visit_mut::VisitMut, Result, Token}; + +/// Parse and expand a `#[dynamic_params(..)]` module. +pub fn dynamic_params(attr: TokenStream, item: TokenStream) -> Result { + DynamicParamModAttr::parse(attr, item).map(ToTokens::into_token_stream) +} + +/// Parse and expand `#[dynamic_pallet_params(..)]` attribute. +pub fn dynamic_pallet_params(attr: TokenStream, item: TokenStream) -> Result { + DynamicPalletParamAttr::parse(attr, item).map(ToTokens::into_token_stream) +} + +/// Parse and expand `#[dynamic_aggregated_params_internal]` attribute. +pub fn dynamic_aggregated_params_internal( + _attr: TokenStream, + item: TokenStream, +) -> Result { + parse2::(item).map(ToTokens::into_token_stream) +} + +/// A top `#[dynamic_params(..)]` attribute together with a mod. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicParamModAttr { + params_mod: syn::ItemMod, + meta: DynamicParamModAttrMeta, +} + +/// The inner meta of a `#[dynamic_params(..)]` attribute. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicParamModAttrMeta { + name: syn::Ident, + _comma: Option, + #[parse_if(_comma.is_some())] + params_pallet: Option, +} + +impl DynamicParamModAttr { + pub fn parse(attr: TokenStream, item: TokenStream) -> Result { + let params_mod = parse2(item)?; + let meta = parse2(attr)?; + Ok(Self { params_mod, meta }) + } + + pub fn inner_mods(&self) -> Vec { + self.params_mod.content.as_ref().map_or(Vec::new(), |(_, items)| { + items + .iter() + .filter_map(|i| match i { + syn::Item::Mod(m) => Some(m), + _ => None, + }) + .cloned() + .collect() + }) + } +} + +impl ToTokens for DynamicParamModAttr { + fn to_tokens(&self, tokens: &mut TokenStream) { + let scrate = match crate_access() { + Ok(path) => path, + Err(err) => return tokens.extend(err), + }; + let (mut params_mod, name) = (self.params_mod.clone(), &self.meta.name); + let dynam_params_ident = ¶ms_mod.ident; + + let mut quoted_enum = quote! {}; + for m in self.inner_mods() { + let aggregate_name = + syn::Ident::new(&m.ident.to_string().to_class_case(), m.ident.span()); + let mod_name = &m.ident; + + let mut attrs = m.attrs.clone(); + attrs.retain(|attr| !attr.path().is_ident("dynamic_pallet_params")); + if let Err(err) = ensure_codec_index(&attrs, m.span()) { + tokens.extend(err.into_compile_error()); + return + } + + quoted_enum.extend(quote! { + #(#attrs)* + #aggregate_name(#dynam_params_ident::#mod_name::Parameters), + }); + } + + // Inject the outer args into the inner `#[dynamic_pallet_params(..)]` attribute. + if let Some(params_pallet) = &self.meta.params_pallet { + MacroInjectArgs { runtime_params: name.clone(), params_pallet: params_pallet.clone() } + .visit_item_mod_mut(&mut params_mod); + } + + tokens.extend(quote! { + #params_mod + + #[#scrate::dynamic_params::dynamic_aggregated_params_internal] + pub enum #name { + #quoted_enum + } + }); + } +} + +/// Ensure there is a `#[codec(index = ..)]` attribute. +fn ensure_codec_index(attrs: &Vec, span: Span) -> Result<()> { + let mut found = false; + + for attr in attrs.iter() { + if attr.path().is_ident("codec") { + let meta: syn::ExprAssign = attr.parse_args()?; + if meta.left.to_token_stream().to_string() == "index" { + found = true; + break + } + } + } + + if !found { + Err(syn::Error::new(span, "Missing explicit `#[codec(index = ..)]` attribute")) + } else { + Ok(()) + } +} + +/// Used to inject arguments into the inner `#[dynamic_pallet_params(..)]` attribute. +/// +/// This allows the outer `#[dynamic_params(..)]` attribute to specify some arguments that dont need +/// to be repeated every time. +struct MacroInjectArgs { + runtime_params: syn::Ident, + params_pallet: syn::Type, +} +impl VisitMut for MacroInjectArgs { + fn visit_item_mod_mut(&mut self, item: &mut syn::ItemMod) { + // Check if the mod has a `#[dynamic_pallet_params(..)]` attribute. + let attr = item.attrs.iter_mut().find(|attr| attr.path().is_ident("dynamic_pallet_params")); + + if let Some(attr) = attr { + match &attr.meta { + syn::Meta::Path(path) => + assert_eq!(path.to_token_stream().to_string(), "dynamic_pallet_params"), + _ => (), + } + + let runtime_params = &self.runtime_params; + let params_pallet = &self.params_pallet; + + attr.meta = syn::parse2::(quote! { + dynamic_pallet_params(#runtime_params, #params_pallet) + }) + .unwrap() + .into(); + } + + visit_mut::visit_item_mod_mut(self, item); + } +} +/// The helper attribute of a `#[dynamic_pallet_params(runtime_params, params_pallet)]` +/// attribute. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicPalletParamAttr { + inner_mod: syn::ItemMod, + meta: DynamicPalletParamAttrMeta, +} + +/// The inner meta of a `#[dynamic_pallet_params(..)]` attribute. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicPalletParamAttrMeta { + runtime_params: syn::Ident, + _comma: Token![,], + parameter_pallet: syn::Type, +} + +impl DynamicPalletParamAttr { + pub fn parse(attr: TokenStream, item: TokenStream) -> Result { + Ok(Self { inner_mod: parse2(item)?, meta: parse2(attr)? }) + } + + pub fn statics(&self) -> Vec { + self.inner_mod.content.as_ref().map_or(Vec::new(), |(_, items)| { + items + .iter() + .filter_map(|i| match i { + syn::Item::Static(s) => Some(s), + _ => None, + }) + .cloned() + .collect() + }) + } +} + +impl ToTokens for DynamicPalletParamAttr { + fn to_tokens(&self, tokens: &mut TokenStream) { + let scrate = match crate_access() { + Ok(path) => path, + Err(err) => return tokens.extend(err), + }; + let (params_mod, parameter_pallet, runtime_params) = + (&self.inner_mod, &self.meta.parameter_pallet, &self.meta.runtime_params); + + let aggregate_name = + syn::Ident::new(¶ms_mod.ident.to_string().to_class_case(), params_mod.ident.span()); + let (mod_name, vis) = (¶ms_mod.ident, ¶ms_mod.vis); + let statics = self.statics(); + + let (mut key_names, mut key_values, mut defaults, mut attrs, mut value_types): ( + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + ) = Default::default(); + + for s in statics.iter() { + if let Err(err) = ensure_codec_index(&s.attrs, s.span()) { + tokens.extend(err.into_compile_error()); + return + } + + key_names.push(&s.ident); + key_values.push(format_ident!("{}Value", &s.ident)); + defaults.push(&s.expr); + attrs.push(&s.attrs); + value_types.push(&s.ty); + } + + let key_ident = syn::Ident::new("ParametersKey", params_mod.ident.span()); + let value_ident = syn::Ident::new("ParametersValue", params_mod.ident.span()); + let runtime_key_ident = format_ident!("{}Key", runtime_params); + let runtime_value_ident = format_ident!("{}Value", runtime_params); + + tokens.extend(quote! { + pub mod #mod_name { + use super::*; + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum Parameters { + #( + #(#attrs)* + #key_names(#key_names, Option<#value_types>), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #key_ident { + #( + #(#attrs)* + #key_names(#key_names), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #value_ident { + #( + #(#attrs)* + #key_names(#value_types), + )* + } + + impl #scrate::traits::dynamic_params::AggregratedKeyValue for Parameters { + type Key = #key_ident; + type Value = #value_ident; + + fn into_parts(self) -> (Self::Key, Option) { + match self { + #( + Parameters::#key_names(key, value) => { + (#key_ident::#key_names(key), value.map(#value_ident::#key_names)) + }, + )* + } + } + } + + #( + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis struct #key_names; + + impl #scrate::__private::Get<#value_types> for #key_names { + fn get() -> #value_types { + match + <#parameter_pallet as + #scrate::storage::StorageMap<#runtime_key_ident, #runtime_value_ident> + >::get(#runtime_key_ident::#aggregate_name(#key_ident::#key_names(#key_names))) + { + Some(#runtime_value_ident::#aggregate_name( + #value_ident::#key_names(inner))) => inner, + Some(_) => { + #scrate::defensive!("Unexpected value type at key - returning default"); + #defaults + }, + None => #defaults, + } + } + } + + impl #scrate::traits::dynamic_params::Key for #key_names { + type Value = #value_types; + type WrappedValue = #key_values; + } + + impl From<#key_names> for #key_ident { + fn from(key: #key_names) -> Self { + #key_ident::#key_names(key) + } + } + + impl TryFrom<#key_ident> for #key_names { + type Error = (); + + fn try_from(key: #key_ident) -> Result { + match key { + #key_ident::#key_names(key) => Ok(key), + _ => Err(()), + } + } + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::sp_runtime::RuntimeDebug, + )] + #vis struct #key_values(pub #value_types); + + impl From<#key_values> for #value_ident { + fn from(value: #key_values) -> Self { + #value_ident::#key_names(value.0) + } + } + + impl From<(#key_names, #value_types)> for Parameters { + fn from((key, value): (#key_names, #value_types)) -> Self { + Parameters::#key_names(key, Some(value)) + } + } + + impl From<#key_names> for Parameters { + fn from(key: #key_names) -> Self { + Parameters::#key_names(key, None) + } + } + + impl TryFrom<#value_ident> for #key_values { + type Error = (); + + fn try_from(value: #value_ident) -> Result { + match value { + #value_ident::#key_names(value) => Ok(#key_values(value)), + _ => Err(()), + } + } + } + + impl From<#key_values> for #value_types { + fn from(value: #key_values) -> Self { + value.0 + } + } + )* + } + }); + } +} + +#[derive(derive_syn_parse::Parse)] +pub struct DynamicParamAggregatedEnum { + aggregated_enum: syn::ItemEnum, +} + +impl ToTokens for DynamicParamAggregatedEnum { + fn to_tokens(&self, tokens: &mut TokenStream) { + let scrate = match crate_access() { + Ok(path) => path, + Err(err) => return tokens.extend(err), + }; + let params_enum = &self.aggregated_enum; + let (name, vis) = (¶ms_enum.ident, ¶ms_enum.vis); + + let (mut indices, mut param_names, mut param_types): (Vec<_>, Vec<_>, Vec<_>) = + Default::default(); + let mut attributes = Vec::new(); + for (i, variant) in params_enum.variants.iter().enumerate() { + indices.push(i); + param_names.push(&variant.ident); + attributes.push(&variant.attrs); + + param_types.push(match &variant.fields { + syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, + _ => { + *tokens = quote! { compile_error!("Only unnamed enum variants with one inner item are supported") }; + return + }, + }); + } + + let params_key_ident = format_ident!("{}Key", params_enum.ident); + let params_value_ident = format_ident!("{}Value", params_enum.ident); + + tokens.extend(quote! { + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::sp_runtime::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #name { + #( + //#[codec(index = #indices)] + #(#attributes)* + #param_names(#param_types), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::sp_runtime::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #params_key_ident { + #( + #(#attributes)* + #param_names(<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::sp_runtime::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #params_value_ident { + #( + #(#attributes)* + #param_names(<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Value), + )* + } + + impl #scrate::traits::dynamic_params::AggregratedKeyValue for #name { + type Key = #params_key_ident; + type Value = #params_value_ident; + + fn into_parts(self) -> (Self::Key, Option) { + match self { + #( + #name::#param_names(parameter) => { + let (key, value) = parameter.into_parts(); + (#params_key_ident::#param_names(key), value.map(#params_value_ident::#param_names)) + }, + )* + } + } + } + + #( + impl ::core::convert::From<<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key> for #params_key_ident { + fn from(key: <#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key) -> Self { + #params_key_ident::#param_names(key) + } + } + + impl ::core::convert::TryFrom<#params_value_ident> for <#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Value { + type Error = (); + + fn try_from(value: #params_value_ident) -> Result { + match value { + #params_value_ident::#param_names(value) => Ok(value), + _ => Err(()), + } + } + } + )* + }); + } +} + +/// Get access to the current crate and convert the error to a compile error. +fn crate_access() -> core::result::Result { + generate_access_from_frame_or_crate("frame-support").map_err(|e| e.to_compile_error()) +} diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 34e68966b82333f56c91c0c32b7573f2fd8284e8..20b8d74310f3e4aca7a5cae61fe3e5d72ec0626c 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -18,12 +18,14 @@ //! Proc macro of Support code for the runtime. #![recursion_limit = "512"] +#![deny(rustdoc::broken_intra_doc_links)] mod benchmark; mod construct_runtime; mod crate_version; mod derive_impl; mod dummy_part_checker; +mod dynamic_params; mod key_prefix; mod match_and_insert; mod no_bound; @@ -890,12 +892,13 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { item.ident != "RuntimeOrigin" && item.ident != "RuntimeHoldReason" && item.ident != "RuntimeFreezeReason" && + item.ident != "RuntimeParameters" && item.ident != "PalletInfo" { return syn::Error::new_spanned( item, "`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \ - `RuntimeTask`, `RuntimeOrigin` or `PalletInfo`", + `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`", ) .to_compile_error() .into() @@ -1685,3 +1688,52 @@ pub fn import_section(attr: TokenStream, tokens: TokenStream) -> TokenStream { } .into() } + +/// Mark a module that contains dynamic parameters. +/// +/// See the `pallet_parameters` for a full example. +/// +/// # Arguments +/// +/// The macro accepts two positional arguments, of which the second is optional. +/// +/// ## Aggregated Enum Name +/// +/// This sets the name that the aggregated Key-Value enum will be named after. Common names would be +/// `RuntimeParameters`, akin to `RuntimeCall`, `RuntimeOrigin` etc. There is no default value for +/// this argument. +/// +/// ## Parameter Storage Backend +/// +/// The second argument provides access to the storage of the parameters. It can either be set on +/// on this attribute, or on the inner ones. If set on both, the inner one takes precedence. +#[proc_macro_attribute] +pub fn dynamic_params(attrs: TokenStream, input: TokenStream) -> TokenStream { + dynamic_params::dynamic_params(attrs.into(), input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} + +/// Define a module inside a [`macro@dynamic_params`] module that contains dynamic parameters. +/// +/// See the `pallet_parameters` for a full example. +/// +/// # Argument +/// +/// This attribute takes one optional argument. The argument can either be put here or on the +/// surrounding `#[dynamic_params]` attribute. If set on both, the inner one takes precedence. +#[proc_macro_attribute] +pub fn dynamic_pallet_params(attrs: TokenStream, input: TokenStream) -> TokenStream { + dynamic_params::dynamic_pallet_params(attrs.into(), input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} + +/// Used internally by [`dynamic_params`]. +#[doc(hidden)] +#[proc_macro_attribute] +pub fn dynamic_aggregated_params_internal(attrs: TokenStream, input: TokenStream) -> TokenStream { + dynamic_params::dynamic_aggregated_params_internal(attrs.into(), input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} diff --git a/substrate/frame/support/procedural/src/pallet/expand/call.rs b/substrate/frame/support/procedural/src/pallet/expand/call.rs index 90974619e8e35c4a642639b3ac1f67954dbfb2b8..f43faba1ee0c8c14cb808b2f64f58446ebe7ffae 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/call.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/call.rs @@ -304,7 +304,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #[doc(hidden)] #[codec(skip)] __Ignore( - #frame_support::__private::sp_std::marker::PhantomData<(#type_use_gen,)>, + ::core::marker::PhantomData<(#type_use_gen,)>, #frame_support::Never, ), #( diff --git a/substrate/frame/support/procedural/src/pallet/expand/event.rs b/substrate/frame/support/procedural/src/pallet/expand/event.rs index 2713f45fc3d54429ccab3ba82972adddd6e13a98..655fc5507d2654cfed4db4553ce5a997e4221125 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/event.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/event.rs @@ -87,7 +87,7 @@ pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream { #[doc(hidden)] #[codec(skip)] __Ignore( - #frame_support::__private::sp_std::marker::PhantomData<(#event_use_gen)>, + ::core::marker::PhantomData<(#event_use_gen)>, #frame_support::Never, ) ); diff --git a/substrate/frame/support/procedural/src/pallet/parse/composite.rs b/substrate/frame/support/procedural/src/pallet/parse/composite.rs index a744756234edea4baa773064dd02600f3d429ff5..c3ac74846bf7c664289dab8d046b51370fe28f5f 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/composite.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/composite.rs @@ -171,7 +171,7 @@ impl CompositeDef { #[doc(hidden)] #[codec(skip)] __Ignore( - #scrate::__private::sp_std::marker::PhantomData, + ::core::marker::PhantomData, ) }); } diff --git a/substrate/frame/support/procedural/src/storage_alias.rs b/substrate/frame/support/procedural/src/storage_alias.rs index c0b4089a2748f0b8be6683e591a36fb496af16a4..06f62768ff80f76e3ffb38e1882eadf105cd5b3d 100644 --- a/substrate/frame/support/procedural/src/storage_alias.rs +++ b/substrate/frame/support/procedural/src/storage_alias.rs @@ -623,7 +623,7 @@ fn generate_storage_instance( quote! { #visibility struct #counter_name< #impl_generics >( - #crate_::__private::sp_std::marker::PhantomData<(#type_generics)> + ::core::marker::PhantomData<(#type_generics)> ) #where_clause; impl<#impl_generics> #crate_::traits::StorageInstance @@ -653,7 +653,7 @@ fn generate_storage_instance( let code = quote! { #[allow(non_camel_case_types)] #visibility struct #name< #impl_generics >( - #crate_::__private::sp_std::marker::PhantomData<(#type_generics)> + ::core::marker::PhantomData<(#type_generics)> ) #where_clause; impl<#impl_generics> #crate_::traits::StorageInstance diff --git a/substrate/frame/support/procedural/tools/Cargo.toml b/substrate/frame/support/procedural/tools/Cargo.toml index 0a046a164b6e1547cf318c96c90f1e48e1b5f551..a75307aca79b6ff241611b49b60c931ee1f83373 100644 --- a/substrate/frame/support/procedural/tools/Cargo.toml +++ b/substrate/frame/support/procedural/tools/Cargo.toml @@ -17,6 +17,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.48", features = ["extra-traits", "full", "visit"] } +quote = { workspace = true } +syn = { features = ["extra-traits", "full", "visit"], workspace = true } frame-support-procedural-tools-derive = { path = "derive" } diff --git a/substrate/frame/support/procedural/tools/derive/Cargo.toml b/substrate/frame/support/procedural/tools/derive/Cargo.toml index 08c6a95a34fc26c7f64286e95f729e41d46b343a..b39d99a822fb7aed533bc7795daa53c903cc2952 100644 --- a/substrate/frame/support/procedural/tools/derive/Cargo.toml +++ b/substrate/frame/support/procedural/tools/derive/Cargo.toml @@ -19,5 +19,5 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.56" -quote = { version = "1.0.28", features = ["proc-macro"] } -syn = { version = "2.0.48", features = ["extra-traits", "full", "parsing", "proc-macro"] } +quote = { features = ["proc-macro"], workspace = true } +syn = { features = ["extra-traits", "full", "parsing", "proc-macro"], workspace = true } diff --git a/substrate/frame/support/procedural/tools/derive/src/lib.rs b/substrate/frame/support/procedural/tools/derive/src/lib.rs index f7c57c08674fcee985fab90225e980675cd1096e..a3590263558f491f4629eaa8b3b7997c1919b5ea 100644 --- a/substrate/frame/support/procedural/tools/derive/src/lib.rs +++ b/substrate/frame/support/procedural/tools/derive/src/lib.rs @@ -19,8 +19,6 @@ //! Use to derive parsing for parsing struct. // end::description[] -#![recursion_limit = "128"] - use proc_macro::TokenStream; use proc_macro2::Span; use quote::quote; diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 0f6cf05959f5113d28a250b53deec1cc7930c67d..cd12da6de54e777bd470eb65741d831a99f81ddb 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -44,7 +44,7 @@ pub mod __private { pub use paste; pub use scale_info; pub use serde; - pub use sp_core::{OpaqueMetadata, Void}; + pub use sp_core::{Get, OpaqueMetadata, Void}; pub use sp_crypto_hashing_proc_macro; pub use sp_inherents; #[cfg(feature = "std")] @@ -175,6 +175,14 @@ pub use frame_support_procedural::storage_alias; pub use frame_support_procedural::derive_impl; +/// Experimental macros for defining dynamic params that can be used in pallet configs. +#[cfg(feature = "experimental")] +pub mod dynamic_params { + pub use frame_support_procedural::{ + dynamic_aggregated_params_internal, dynamic_pallet_params, dynamic_params, + }; +} + /// Create new implementations of the [`Get`](crate::traits::Get) trait. /// /// The so-called parameter type can be created in four different ways: @@ -772,7 +780,7 @@ macro_rules! assert_err_with_weight { $crate::assert_err!($call.map(|_| ()).map_err(|e| e.error), $err); assert_eq!(dispatch_err_with_post.post_info.actual_weight, $weight); } else { - panic!("expected Err(_), got Ok(_).") + ::core::panic!("expected Err(_), got Ok(_).") } }; } diff --git a/substrate/frame/support/src/tests/mod.rs b/substrate/frame/support/src/tests/mod.rs index c6a0b6cde7737e7510ab124bacef86a1a55aca96..c63bfb181c3ff0ae56197432046dc381761f4dae 100644 --- a/substrate/frame/support/src/tests/mod.rs +++ b/substrate/frame/support/src/tests/mod.rs @@ -171,7 +171,7 @@ pub mod frame_system { pub data: Vec<(u32, u64)>, pub test_config: Vec<(u32, u32, u64)>, #[serde(skip)] - pub _config: sp_std::marker::PhantomData, + pub _config: core::marker::PhantomData, } impl Default for GenesisConfig { diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 2a42fca76b3c57623c74e3724b342eadf57737cd..3d0429f71b11d6c74f87ef33521634f46b0d591c 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -124,6 +124,8 @@ pub use safe_mode::{SafeMode, SafeModeError, SafeModeNotify}; mod tx_pause; pub use tx_pause::{TransactionPause, TransactionPauseError}; +pub mod dynamic_params; + pub mod tasks; pub use tasks::Task; diff --git a/substrate/frame/support/src/traits/dispatch.rs b/substrate/frame/support/src/traits/dispatch.rs index facc35a77ae09fc325cc4f4292a8776f1c8438ff..de50ce7a26c211190ddcb81976d39b9779540756 100644 --- a/substrate/frame/support/src/traits/dispatch.rs +++ b/substrate/frame/support/src/traits/dispatch.rs @@ -19,11 +19,11 @@ use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; use codec::MaxEncodedLen; +use core::{cmp::Ordering, marker::PhantomData}; use sp_runtime::{ traits::{BadOrigin, Get, Member, Morph, TryMorph}, Either, }; -use sp_std::{cmp::Ordering, marker::PhantomData}; use super::misc; @@ -85,7 +85,7 @@ pub trait EnsureOrigin { /// ```rust /// # use frame_support::traits::{EnsureOriginEqualOrHigherPrivilege, PrivilegeCmp, EnsureOrigin as _}; /// # use sp_runtime::traits::{parameter_types, Get}; -/// # use sp_std::cmp::Ordering; +/// # use core::cmp::Ordering; /// /// #[derive(Eq, PartialEq, Debug)] /// pub enum Origin { @@ -124,7 +124,7 @@ pub trait EnsureOrigin { /// assert!(EnsureOrigin::ensure_origin(Origin::NormalUser).is_err()); /// ``` pub struct EnsureOriginEqualOrHigherPrivilege( - sp_std::marker::PhantomData<(Origin, PrivilegeCmp)>, + core::marker::PhantomData<(Origin, PrivilegeCmp)>, ); impl EnsureOrigin @@ -218,7 +218,7 @@ macro_rules! impl_ensure_origin_with_arg_ignoring_arg { } /// [`EnsureOrigin`] implementation that always fails. -pub struct NeverEnsureOrigin(sp_std::marker::PhantomData); +pub struct NeverEnsureOrigin(core::marker::PhantomData); impl EnsureOrigin for NeverEnsureOrigin { type Success = Success; fn try_origin(o: OO) -> Result { @@ -235,7 +235,7 @@ impl_ensure_origin_with_arg_ignoring_arg! { {} } -pub struct AsEnsureOriginWithArg(sp_std::marker::PhantomData); +pub struct AsEnsureOriginWithArg(core::marker::PhantomData); impl> EnsureOriginWithArg for AsEnsureOriginWithArg { @@ -353,7 +353,7 @@ impl< /// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. /// /// Successful origin is derived from the left side. -pub struct EitherOfDiverse(sp_std::marker::PhantomData<(L, R)>); +pub struct EitherOfDiverse(core::marker::PhantomData<(L, R)>); impl, R: EnsureOrigin> EnsureOrigin for EitherOfDiverse { @@ -402,7 +402,7 @@ pub type EnsureOneOf = EitherOfDiverse; /// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. /// /// Successful origin is derived from the left side. -pub struct EitherOf(sp_std::marker::PhantomData<(L, R)>); +pub struct EitherOf(core::marker::PhantomData<(L, R)>); impl< OuterOrigin, L: EnsureOrigin, diff --git a/substrate/frame/support/src/traits/dynamic_params.rs b/substrate/frame/support/src/traits/dynamic_params.rs new file mode 100644 index 0000000000000000000000000000000000000000..8881df04141cc13a97f79bc6df3ed41b8db0323d --- /dev/null +++ b/substrate/frame/support/src/traits/dynamic_params.rs @@ -0,0 +1,153 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Types and traits for dynamic parameters. +//! +//! Can be used by 3rd party macros to define dynamic parameters that are compatible with the the +//! `parameters` pallet. + +use codec::MaxEncodedLen; +use frame_support::Parameter; + +/// A dynamic parameter store across an aggregated KV type. +pub trait RuntimeParameterStore { + type AggregratedKeyValue: AggregratedKeyValue; + + /// Get the value of a parametrized key. + /// + /// Should return `None` if no explicit value was set instead of a default. + fn get(key: K) -> Option + where + KV: AggregratedKeyValue, + K: Key + Into<::Key>, + ::Key: IntoKey< + <::AggregratedKeyValue as AggregratedKeyValue>::Key, + >, + <::AggregratedKeyValue as AggregratedKeyValue>::Value: + TryIntoKey<::Value>, + ::Value: TryInto; +} + +/// A dynamic parameter store across a concrete KV type. +pub trait ParameterStore { + /// Get the value of a parametrized key. + fn get(key: K) -> Option + where + K: Key + Into<::Key>, + ::Value: TryInto; +} + +/// Key of a dynamic parameter. +pub trait Key { + /// The value that the key is parametrized with. + type Value; + + /// An opaque representation of `Self::Value`. + type WrappedValue: Into; +} + +/// The aggregated key-value type of a dynamic parameter store. +pub trait AggregratedKeyValue: Parameter { + /// The aggregated key type. + type Key: Parameter + MaxEncodedLen; + + /// The aggregated value type. + type Value: Parameter + MaxEncodedLen; + + /// Split the aggregated key-value type into its parts. + fn into_parts(self) -> (Self::Key, Option); +} + +impl AggregratedKeyValue for () { + type Key = (); + type Value = (); + + fn into_parts(self) -> (Self::Key, Option) { + ((), None) + } +} + +/// Allows to create a `ParameterStore` from a `RuntimeParameterStore`. +/// +/// This concretization is useful when configuring pallets, since a pallet will require a parameter +/// store for its own KV type and not the aggregated runtime-wide KV type. +pub struct ParameterStoreAdapter(sp_std::marker::PhantomData<(PS, KV)>); + +impl ParameterStore for ParameterStoreAdapter +where + PS: RuntimeParameterStore, + KV: AggregratedKeyValue, + ::Key: + IntoKey<<::AggregratedKeyValue as AggregratedKeyValue>::Key>, + ::Value: TryFromKey< + <::AggregratedKeyValue as AggregratedKeyValue>::Value, + >, +{ + fn get(key: K) -> Option + where + K: Key + Into<::Key>, + ::Value: TryInto, + { + PS::get::(key) + } +} + +// workaround for rust bug https://github.com/rust-lang/rust/issues/51445 +mod workaround { + pub trait FromKey: Sized { + #[must_use] + fn from_key(value: T) -> Self; + } + + pub trait IntoKey: Sized { + #[must_use] + fn into_key(self) -> T; + } + + impl IntoKey for T + where + U: FromKey, + { + fn into_key(self) -> U { + U::from_key(self) + } + } + + pub trait TryIntoKey: Sized { + type Error; + + fn try_into_key(self) -> Result; + } + + pub trait TryFromKey: Sized { + type Error; + + fn try_from_key(value: T) -> Result; + } + + impl TryIntoKey for T + where + U: TryFromKey, + { + type Error = U::Error; + + fn try_into_key(self) -> Result { + U::try_from_key(self) + } + } +} +pub use workaround::*; diff --git a/substrate/frame/support/src/traits/error.rs b/substrate/frame/support/src/traits/error.rs index 0f30e266da2dffebef980088e332b312187081ab..7b577fe9995a4d304c991efe92055c779e71c445 100644 --- a/substrate/frame/support/src/traits/error.rs +++ b/substrate/frame/support/src/traits/error.rs @@ -17,7 +17,7 @@ //! Traits for describing and constraining pallet error types. use codec::{Compact, Decode, Encode}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Trait indicating that the implementing type is going to be included as a field in a variant of /// the `#[pallet::error]` enum type. diff --git a/substrate/frame/support/src/traits/stored_map.rs b/substrate/frame/support/src/traits/stored_map.rs index cbe70f29323491179d945027bfccaaf5cb880671..15df2ef0af55d49c28d271aef10f4ae018163f11 100644 --- a/substrate/frame/support/src/traits/stored_map.rs +++ b/substrate/frame/support/src/traits/stored_map.rs @@ -81,7 +81,7 @@ pub trait StoredMap { /// be the default value), or where the account is being removed or reset back to the default value /// where previously it did exist (though may have been in a default state). This works well with /// system module's `CallOnCreatedAccount` and `CallKillAccount`. -pub struct StorageMapShim(sp_std::marker::PhantomData<(S, K, T)>); +pub struct StorageMapShim(core::marker::PhantomData<(S, K, T)>); impl, K: FullCodec, T: FullCodec + Default> StoredMap for StorageMapShim { diff --git a/substrate/frame/support/src/traits/tokens/currency.rs b/substrate/frame/support/src/traits/tokens/currency.rs index 0030e1261dac1be87f572b1ee26220b2992798cd..8b773115011de27b9920de70860491ec404a54e1 100644 --- a/substrate/frame/support/src/traits/tokens/currency.rs +++ b/substrate/frame/support/src/traits/tokens/currency.rs @@ -211,7 +211,7 @@ pub trait Currency { /// A non-const `Get` implementation parameterised by a `Currency` impl which provides the result /// of `total_issuance`. -pub struct TotalIssuanceOf, A>(sp_std::marker::PhantomData<(C, A)>); +pub struct TotalIssuanceOf, A>(core::marker::PhantomData<(C, A)>); impl, A> Get for TotalIssuanceOf { fn get() -> C::Balance { C::total_issuance() @@ -220,7 +220,7 @@ 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)>); +pub struct ActiveIssuanceOf, A>(core::marker::PhantomData<(C, A)>); impl, A> Get for ActiveIssuanceOf { fn get() -> C::Balance { C::active_issuance() diff --git a/substrate/frame/support/src/traits/tokens/imbalance.rs b/substrate/frame/support/src/traits/tokens/imbalance.rs index 9dd8531324dc9d4a2845fd40b09a0d32746a99b6..8eb9b355a4cf6677b1a9f3e1eb77a879a6e0dc70 100644 --- a/substrate/frame/support/src/traits/tokens/imbalance.rs +++ b/substrate/frame/support/src/traits/tokens/imbalance.rs @@ -19,8 +19,8 @@ //! with unbalanced operations. use crate::traits::misc::{SameOrOther, TryDrop}; +use core::ops::Div; use sp_runtime::traits::Saturating; -use sp_std::ops::Div; mod on_unbalanced; mod signed_imbalance; diff --git a/substrate/frame/support/test/Cargo.toml b/substrate/frame/support/test/Cargo.toml index 8b61f25f569aa51d3ac221f15d7f3ffc156b6739..ae2c56a531fd428e712a0825153afc7c89185c78 100644 --- a/substrate/frame/support/test/Cargo.toml +++ b/substrate/frame/support/test/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] static_assertions = "1.1.0" -serde = { version = "1.0.195", default-features = false, features = ["derive"] } +serde = { features = ["derive"], workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-metadata = { version = "16.0.0", default-features = false, features = ["current"] } diff --git a/substrate/frame/support/test/pallet/Cargo.toml b/substrate/frame/support/test/pallet/Cargo.toml index 48adbcab5826157ea3edfb4eb931f77b9b9794a1..ca889faef876b197cb15a338e0a65b30536b79ec 100644 --- a/substrate/frame/support/test/pallet/Cargo.toml +++ b/substrate/frame/support/test/pallet/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["derive"] } +serde = { features = ["derive"], workspace = true } frame-support = { path = "../..", default-features = false } frame-system = { path = "../../../system", default-features = false } sp-runtime = { path = "../../../../primitives/runtime", default-features = false } diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr index 3b6329c650fa65208e01c7d991e321f256b7eecf..6160f8234a350cc3ee0a0761cab0eed6ea022525 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr @@ -4,88 +4,24 @@ error: The number of pallets exceeds the maximum number of tuple elements. To in 67 | pub struct Runtime | ^^^ -error[E0412]: cannot find type `RuntimeCall` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:35:64 - | -35 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; - | ^^^^^^^^^^^ not found in this scope - | -help: you might be missing a type parameter - | -35 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; - | +++++++++++++ - -error[E0412]: cannot find type `Runtime` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:37:25 - | -37 | impl pallet::Config for Runtime {} - | ^^^^^^^ not found in this scope - -error[E0412]: cannot find type `Runtime` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:40:31 - | -40 | impl frame_system::Config for Runtime { - | ^^^^^^^ not found in this scope - -error[E0412]: cannot find type `RuntimeOrigin` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:42:23 - | -42 | type RuntimeOrigin = RuntimeOrigin; - | ^^^^^^^^^^^^^ - | -help: you might have meant to use the associated type - | -42 | type RuntimeOrigin = Self::RuntimeOrigin; - | ++++++ - -error[E0412]: cannot find type `RuntimeCall` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:44:21 - | -44 | type RuntimeCall = RuntimeCall; - | ^^^^^^^^^^^ - | -help: you might have meant to use the associated type - | -44 | type RuntimeCall = Self::RuntimeCall; - | ++++++ - -error[E0412]: cannot find type `RuntimeEvent` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:50:22 - | -50 | type RuntimeEvent = RuntimeEvent; - | ^^^^^^^^^^^^ - | -help: you might have meant to use the associated type - | -50 | type RuntimeEvent = Self::RuntimeEvent; - | ++++++ - -error[E0412]: cannot find type `PalletInfo` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:56:20 - | -56 | type PalletInfo = PalletInfo; - | ^^^^^^^^^^ - | -help: you might have meant to use the associated type - | -56 | type PalletInfo = Self::PalletInfo; - | ++++++ -help: consider importing one of these items - | -18 + use frame_benchmarking::__private::traits::PalletInfo; - | -18 + use frame_support::traits::PalletInfo; - | - -error[E0412]: cannot find type `RuntimeTask` in this scope - --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:39:1 - | -39 | #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the macro `frame_system::config_preludes::TestDefaultConfig` which comes from the expansion of the macro `frame_support::macro_magic::forward_tokens_verbatim` (in Nightly builds, run with -Z macro-backtrace for more info) -help: you might have meant to use the associated type - --> $WORKSPACE/substrate/frame/system/src/lib.rs - | - | type Self::RuntimeTask = (); - | ++++++ +error: recursion limit reached while expanding `frame_support::__private::tt_return!` + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:22:1 + | +22 | / #[frame_support::pallet] +23 | | mod pallet { +24 | | #[pallet::config] +25 | | pub trait Config: frame_system::Config {} +... | +66 | |/ construct_runtime! { +67 | || pub struct Runtime +68 | || { +69 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, +... || +180 | || } +181 | || } + | ||_^ + | |_| + | in this macro invocation + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`$CRATE`) + = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr index cda20288984ae535c0755fd1e6814384d124c515..c7159b34afb3d22737fb5d8ed6662027f04a3bd6 100644 --- a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr +++ b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr @@ -1,4 +1,4 @@ -error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin` or `PalletInfo` +error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo` --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5 | 32 | type RuntimeInfo = (); diff --git a/substrate/frame/system/Cargo.toml b/substrate/frame/system/Cargo.toml index d4094601314873671a1c749c72b91a27452b5702..416969e9c4776aa161dde65281b7847156bdbf56 100644 --- a/substrate/frame/system/Cargo.toml +++ b/substrate/frame/system/Cargo.toml @@ -18,9 +18,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] cfg-if = "1.0" codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive", "serde"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"] } +serde = { features = ["alloc", "derive"], workspace = true } frame-support = { path = "../support", default-features = false } sp-core = { path = "../../primitives/core", default-features = false, features = ["serde"] } sp-io = { path = "../../primitives/io", default-features = false } diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 069217bcee46b4663c836a750bfd90a568c1a4d2..1405c7303f870e1c5f49d6766d4a8fd39412a5e9 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -148,6 +148,7 @@ use sp_io::TestExternalities; pub mod limits; #[cfg(test)] pub(crate) mod mock; + pub mod offchain; mod extensions; @@ -847,7 +848,7 @@ pub mod pallet { #[pallet::storage] #[pallet::whitelist_storage] #[pallet::getter(fn block_weight)] - pub(super) type BlockWeight = StorageValue<_, ConsumedWeight, ValueQuery>; + pub type BlockWeight = StorageValue<_, ConsumedWeight, ValueQuery>; /// Total length (in bytes) for all extrinsics put together, for the current block. #[pallet::storage] diff --git a/substrate/frame/system/src/mock.rs b/substrate/frame/system/src/mock.rs index 06527a9bb2912b4253186307456ad0885cf2c17e..c4108099e39ffdf306d7a9e5e0af8a619336eaea 100644 --- a/substrate/frame/system/src/mock.rs +++ b/substrate/frame/system/src/mock.rs @@ -16,15 +16,8 @@ // limitations under the License. use crate::{self as frame_system, *}; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, Perbill, -}; +use frame_support::{derive_impl, parameter_types}; +use sp_runtime::{BuildStorage, Perbill}; type Block = mocking::MockBlock; @@ -87,29 +80,12 @@ impl OnKilledAccount for RecordKilled { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl Config for Test { - type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; type BlockLength = RuntimeBlockLength; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<10>; - type DbWeight = DbWeight; type Version = Version; - type PalletInfo = PalletInfo; type AccountData = u32; - type OnNewAccount = (); type OnKilledAccount = RecordKilled; - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } pub type SysEvent = frame_system::Event; diff --git a/substrate/frame/timestamp/Cargo.toml b/substrate/frame/timestamp/Cargo.toml index cd0737c6bb8fea31471e7e9ba14637588b33c4de..28e57fcab0a79d18ea8d8a1b2b8be90cb05198db 100644 --- a/substrate/frame/timestamp/Cargo.toml +++ b/substrate/frame/timestamp/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/timestamp/src/mock.rs b/substrate/frame/timestamp/src/mock.rs index 5cbc5211368d4e6ed8027b56a06a49f02f1d3f86..244b66a4bb2c64428011b1e90fea0ec76d5ba6f2 100644 --- a/substrate/frame/timestamp/src/mock.rs +++ b/substrate/frame/timestamp/src/mock.rs @@ -20,16 +20,9 @@ use super::*; use crate as pallet_timestamp; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, ConstU64}, -}; -use sp_core::H256; +use frame_support::{derive_impl, parameter_types, traits::ConstU64}; use sp_io::TestExternalities; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; type Moment = u64; @@ -44,29 +37,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } parameter_types! { diff --git a/substrate/frame/tips/Cargo.toml b/substrate/frame/tips/Cargo.toml index 900cd47e0dc02afaad237b1de8c1e98c6941e672..7339cf0a8cce86ec15aebe6a55115f10360b1a7f 100644 --- a/substrate/frame/tips/Cargo.toml +++ b/substrate/frame/tips/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/tips/src/migrations/v4.rs b/substrate/frame/tips/src/migrations/v4.rs index 2404c6de1a16bb0657bd7771a3019186db8d0976..8b0e65c58dbde4b754892b09da93187a25dec087 100644 --- a/substrate/frame/tips/src/migrations/v4.rs +++ b/substrate/frame/tips/src/migrations/v4.rs @@ -15,8 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use core::str; use sp_io::hashing::twox_128; -use sp_std::str; use super::super::LOG_TARGET; use frame_support::{ diff --git a/substrate/frame/tips/src/tests.rs b/substrate/frame/tips/src/tests.rs index 9d4047cd80eabb3c7c2b36382c8ec1020fc2bd6b..0e7ea1f47817c5ae14dace2dce7ce7e555a68a03 100644 --- a/substrate/frame/tips/src/tests.rs +++ b/substrate/frame/tips/src/tests.rs @@ -59,29 +59,10 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/transaction-payment/Cargo.toml b/substrate/frame/transaction-payment/Cargo.toml index 4b30f8e755f5926f1cae65d73a022426bcccded9..275e1c01f927217726907f3e6045fc0b0d3681e5 100644 --- a/substrate/frame/transaction-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } sp-core = { path = "../../primitives/core", default-features = false } @@ -29,7 +29,7 @@ sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } [dev-dependencies] -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } pallet-balances = { path = "../balances" } [features] diff --git a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml index f7114e8f24b6ffc8c2654895104b682457c5c45e..1da3237df0817c989899b9180af984da9766aa81 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -30,10 +30,10 @@ frame-benchmarking = { path = "../../benchmarking", default-features = false, op # Other dependencies codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } [dev-dependencies] -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } sp-storage = { path = "../../../primitives/storage", default-features = false } diff --git a/substrate/frame/transaction-payment/rpc/Cargo.toml b/substrate/frame/transaction-payment/rpc/Cargo.toml index e7edf8a4b87920702d9669e62d611c2349bd20e4..6d7f632af82813050d4c0523dcd61dc27ea512f1 100644 --- a/substrate/frame/transaction-payment/rpc/Cargo.toml +++ b/substrate/frame/transaction-payment/rpc/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } pallet-transaction-payment-rpc-runtime-api = { path = "runtime-api" } sp-api = { path = "../../../primitives/api" } sp-blockchain = { path = "../../../primitives/blockchain" } diff --git a/substrate/frame/transaction-payment/src/payment.rs b/substrate/frame/transaction-payment/src/payment.rs index 22b0ac7c742403ac068b64a75a89e552ef06c470..886683f2e0b883aea0ccd4e769c902ee45bdf99a 100644 --- a/substrate/frame/transaction-payment/src/payment.rs +++ b/substrate/frame/transaction-payment/src/payment.rs @@ -18,11 +18,11 @@ /// ! Traits and default implementation for paying transaction fees. use crate::Config; +use core::marker::PhantomData; use sp_runtime::{ traits::{DispatchInfoOf, PostDispatchInfoOf, Saturating, Zero}, transaction_validity::InvalidTransaction, }; -use sp_std::marker::PhantomData; use frame_support::{ traits::{Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReasons}, diff --git a/substrate/frame/transaction-storage/Cargo.toml b/substrate/frame/transaction-storage/Cargo.toml index c96aa91d54a8287f66ebccaa4493f8d69ab496d5..1386d9b5a5691475bdc06a198a86cb944b7e8305 100644 --- a/substrate/frame/transaction-storage/Cargo.toml +++ b/substrate/frame/transaction-storage/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = { version = "6.1", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", optional = true } +serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } @@ -29,7 +29,7 @@ sp-io = { path = "../../primitives/io", default-features = false } sp-runtime = { path = "../../primitives/runtime", default-features = false } sp-std = { path = "../../primitives/std", default-features = false } sp-transaction-storage-proof = { path = "../../primitives/transaction-storage-proof", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] sp-core = { path = "../../primitives/core", default-features = false } diff --git a/substrate/frame/treasury/Cargo.toml b/substrate/frame/treasury/Cargo.toml index 2dc603e9f9b5bc7e4f82795dd9aedcc5f21d5393..5f90904123d9341425ff707ae7682e2f20f28ecf 100644 --- a/substrate/frame/treasury/Cargo.toml +++ b/substrate/frame/treasury/Cargo.toml @@ -23,7 +23,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = docify = "0.2.7" impl-trait-for-tuples = "0.2.2" scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } frame-system = { path = "../system", default-features = false } diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index e35d50e23a3424ddb17c03acc89d42baf7429bb4..b488300de99d5d0c77d8496b597f71c03935b61e 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -20,9 +20,8 @@ #![cfg(test)] use core::{cell::RefCell, marker::PhantomData}; -use sp_core::H256; use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, Dispatchable, IdentityLookup}, + traits::{BadOrigin, Dispatchable, IdentityLookup}, BuildStorage, }; @@ -56,29 +55,10 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { type MaxLocks = (); diff --git a/substrate/frame/uniques/Cargo.toml b/substrate/frame/uniques/Cargo.toml index 8a5a180d75f3ab03bb0ecce8c7a337dc9f3e0c17..4e5f21b3d8df8fa45da0735cdd0ebe1afac6d181 100644 --- a/substrate/frame/uniques/Cargo.toml +++ b/substrate/frame/uniques/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/uniques/src/migration.rs b/substrate/frame/uniques/src/migration.rs index 6b2bbf375e7541d58a30af413b6aad288d77e655..ba0855a6bb657698d5c642181c3483459774a04f 100644 --- a/substrate/frame/uniques/src/migration.rs +++ b/substrate/frame/uniques/src/migration.rs @@ -17,8 +17,8 @@ //! Various pieces of common functionality. use super::*; +use core::marker::PhantomData; use frame_support::traits::{Get, OnRuntimeUpgrade}; -use sp_std::marker::PhantomData; mod v1 { use super::*; diff --git a/substrate/frame/uniques/src/mock.rs b/substrate/frame/uniques/src/mock.rs index 16da2b2a2e285af87d1fb392f5b17b1022ad4ef2..eae125971635ec7d3b6ed7a8559e0a5e018592f7 100644 --- a/substrate/frame/uniques/src/mock.rs +++ b/substrate/frame/uniques/src/mock.rs @@ -24,11 +24,7 @@ use frame_support::{ construct_runtime, derive_impl, traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -43,29 +39,8 @@ construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/utility/src/tests.rs b/substrate/frame/utility/src/tests.rs index 1a1196cb4c0669c279b47c48e309101e9d27e3d9..8742513be95043186dbe840540247fe7160fdfaa 100644 --- a/substrate/frame/utility/src/tests.rs +++ b/substrate/frame/utility/src/tests.rs @@ -27,13 +27,12 @@ use frame_support::{ dispatch::{DispatchErrorWithPostInfo, Pays}, error::BadOrigin, parameter_types, storage, - traits::{ConstU32, ConstU64, Contains}, + traits::{ConstU64, Contains}, weights::Weight, }; use pallet_collective::{EnsureProportionAtLeast, Instance1}; -use sp_core::H256; use sp_runtime::{ - traits::{BlakeTwo256, Dispatchable, Hash, IdentityLookup}, + traits::{BlakeTwo256, Dispatchable, Hash}, BuildStorage, DispatchError, TokenError, }; @@ -148,27 +147,8 @@ parameter_types! { impl frame_system::Config for Test { type BaseCallFilter = TestBaseCallFilter; type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = frame_system::weights::SubstrateWeight; - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/frame/vesting/Cargo.toml b/substrate/frame/vesting/Cargo.toml index f81b7a122c57b82c8a8b841ca72e56d5fb275f48..96938b95a2ad4c09f3ec821ecc14c5cc3bc68270 100644 --- a/substrate/frame/vesting/Cargo.toml +++ b/substrate/frame/vesting/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } frame-support = { path = "../support", default-features = false } diff --git a/substrate/frame/vesting/src/mock.rs b/substrate/frame/vesting/src/mock.rs index befe8cd3b760eddce92f7de73893df753b71546b..8a0cd1351253663983774a7a79d17dd60583b3f5 100644 --- a/substrate/frame/vesting/src/mock.rs +++ b/substrate/frame/vesting/src/mock.rs @@ -17,13 +17,9 @@ use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, WithdrawReasons}, -}; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, Identity, IdentityLookup}, - BuildStorage, + traits::{ConstU32, WithdrawReasons}, }; +use sp_runtime::{traits::Identity, BuildStorage}; use super::*; use crate as pallet_vesting; @@ -42,28 +38,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type AccountData = pallet_balances::AccountData; - type AccountId = u64; - type BaseCallFilter = frame_support::traits::Everything; - type BlockHashCount = ConstU64<250>; - type BlockLength = (); - type BlockWeights = (); - type RuntimeCall = RuntimeCall; - type DbWeight = (); - type RuntimeEvent = RuntimeEvent; - type Hash = H256; - type Hashing = BlakeTwo256; type Block = Block; - type Nonce = u64; - type Lookup = IdentityLookup; - type OnKilledAccount = (); - type OnNewAccount = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - type RuntimeOrigin = RuntimeOrigin; - type PalletInfo = PalletInfo; - type SS58Prefix = (); - type SystemWeightInfo = (); - type Version = (); } impl pallet_balances::Config for Test { diff --git a/substrate/frame/whitelist/src/mock.rs b/substrate/frame/whitelist/src/mock.rs index c0c38075f298909e7f918de1d74514ee0f86e16d..e323e806b8152b3765f60af46a59ec151f147154 100644 --- a/substrate/frame/whitelist/src/mock.rs +++ b/substrate/frame/whitelist/src/mock.rs @@ -21,16 +21,9 @@ use crate as pallet_whitelist; -use frame_support::{ - construct_runtime, derive_impl, - traits::{ConstU32, ConstU64, Nothing}, -}; +use frame_support::{construct_runtime, derive_impl, traits::ConstU64}; use frame_system::EnsureRoot; -use sp_core::H256; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; +use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -46,29 +39,8 @@ construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = Nothing; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Nonce = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; type Block = Block; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index a1438316289dd660eedac3da01e25d30e205b2ea..f4b1d13c520399a9d5960bf90958fdd5b154d10a 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -21,15 +21,18 @@ sp-api-proc-macro = { path = "proc-macro", default-features = false } sp-core = { path = "../core", default-features = false } sp-std = { path = "../std", default-features = false } sp-runtime = { path = "../runtime", default-features = false } +sp-runtime-interface = { path = "../runtime-interface", default-features = false } sp-externalities = { path = "../externalities", default-features = false, optional = true } sp-version = { path = "../version", default-features = false } sp-state-machine = { path = "../state-machine", default-features = false, optional = true } sp-trie = { path = "../trie", default-features = false, optional = true } hash-db = { version = "0.16.0", optional = true } -thiserror = { version = "1.0.48", optional = true } -scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +thiserror = { optional = true, workspace = true } +scale-info = { version = "2.10.0", default-features = false, features = [ + "derive", +] } sp-metadata-ir = { path = "../metadata-ir", default-features = false, optional = true } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] sp-test-primitives = { path = "../test-primitives" } @@ -46,6 +49,7 @@ std = [ "sp-externalities", "sp-externalities?/std", "sp-metadata-ir?/std", + "sp-runtime-interface/std", "sp-runtime/std", "sp-state-machine/std", "sp-std/std", diff --git a/substrate/primitives/api/proc-macro/Cargo.toml b/substrate/primitives/api/proc-macro/Cargo.toml index 166ce55e5151c48267b799e18a403a7d0b5337b6..f5406758687ae20e9ce13a2cf02959c5cccbd576 100644 --- a/substrate/primitives/api/proc-macro/Cargo.toml +++ b/substrate/primitives/api/proc-macro/Cargo.toml @@ -19,8 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.28" -syn = { version = "2.0.48", features = ["extra-traits", "fold", "full", "visit"] } +quote = { workspace = true } +syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } proc-macro2 = "1.0.56" blake2 = { version = "0.10.4", default-features = false } proc-macro-crate = "3.0.0" diff --git a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs index fd81fdb624c1f33bef6d5b71e759648e75899064..b7e5600a017a9978fb20286ad5724fc21936ca59 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -237,12 +237,13 @@ fn generate_wasm_interface(impls: &[ItemImpl]) -> Result { #c::std_disabled! { #( #attrs )* #[no_mangle] - pub unsafe fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 { + #[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #c::__private::polkavm_export(abi = #c::__private::polkavm_abi))] + pub unsafe extern fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 { let mut #input = if input_len == 0 { &[0u8; 0] } else { unsafe { - #c::slice::from_raw_parts(input_data, input_len) + ::core::slice::from_raw_parts(input_data, input_len) } }; @@ -344,7 +345,7 @@ fn generate_runtime_api_base_structures() -> Result { &self, backend: &B, parent_hash: Block::Hash, - ) -> core::result::Result< + ) -> ::core::result::Result< #crate_::StorageChanges, String > where Self: Sized { diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index 0a9b334a96f8ea0cfdcd48c4cebbfd88e4ddfe24..190de1ab3fdee0611f27071b9c769a164c3aca9d 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -105,6 +105,9 @@ pub mod __private { }; pub use sp_std::{mem, slice, vec}; pub use sp_version::{create_apis_vec, ApiId, ApisVec, RuntimeVersion}; + + #[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))] + pub use sp_runtime_interface::polkavm::{polkavm_abi, polkavm_export}; } #[cfg(feature = "std")] diff --git a/substrate/primitives/api/test/Cargo.toml b/substrate/primitives/api/test/Cargo.toml index b0975082c44ecde816cfc332c940dfbd1783b04b..3a90553bbf000c1b1ea70ae11e7532a456f1c2f2 100644 --- a/substrate/primitives/api/test/Cargo.toml +++ b/substrate/primitives/api/test/Cargo.toml @@ -31,7 +31,7 @@ scale-info = { version = "2.10.0", default-features = false, features = ["derive [dev-dependencies] criterion = "0.4.0" futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } sp-core = { path = "../../core" } static_assertions = "1.1.0" diff --git a/substrate/primitives/application-crypto/Cargo.toml b/substrate/primitives/application-crypto/Cargo.toml index 2b38b59b4dad5b9d2b0633b772fe7aa80cc97cdf..6f90a2b6262e50d7edd23e415924a8259f0a0d96 100644 --- a/substrate/primitives/application-crypto/Cargo.toml +++ b/substrate/primitives/application-crypto/Cargo.toml @@ -21,7 +21,7 @@ targets = ["x86_64-unknown-linux-gnu"] sp-core = { path = "../core", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } sp-std = { path = "../std", default-features = false } sp-io = { path = "../io", default-features = false } diff --git a/substrate/primitives/arithmetic/Cargo.toml b/substrate/primitives/arithmetic/Cargo.toml index 332646710eab1511cd3980086ac833a123418608..301821ad6893117661e439921f23d4d5d48f16b5 100644 --- a/substrate/primitives/arithmetic/Cargo.toml +++ b/substrate/primitives/arithmetic/Cargo.toml @@ -24,7 +24,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = integer-sqrt = "0.1.2" num-traits = { version = "0.2.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } static_assertions = "1.1.0" sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/arithmetic/src/helpers_128bit.rs b/substrate/primitives/arithmetic/src/helpers_128bit.rs index 4e234d4026c9aa59091fba7a0bb9e6da28455a7b..cbbad58443f92332277210f95a032c0bf5ed8e1a 100644 --- a/substrate/primitives/arithmetic/src/helpers_128bit.rs +++ b/substrate/primitives/arithmetic/src/helpers_128bit.rs @@ -22,7 +22,7 @@ //! multiplication implementation provided there. use crate::{biguint, Rounding}; -use sp_std::cmp::{max, min}; +use core::cmp::{max, min}; /// Helper gcd function used in Rational128 implementation. pub fn gcd(a: u128, b: u128) -> u128 { diff --git a/substrate/primitives/arithmetic/src/traits.rs b/substrate/primitives/arithmetic/src/traits.rs index 6fcc8248539ca1649a11014615ad5ab48da53119..66ec19c1b573a5b308d68555713d618e2595959b 100644 --- a/substrate/primitives/arithmetic/src/traits.rs +++ b/substrate/primitives/arithmetic/src/traits.rs @@ -18,6 +18,9 @@ //! Primitive traits for the runtime arithmetic. use codec::HasCompact; +use core::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, Shr, Sub, SubAssign, +}; pub use ensure::{ ensure_pow, Ensure, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureDivAssign, EnsureFixedPointNumber, EnsureFrom, EnsureInto, EnsureMul, EnsureMulAssign, EnsureOp, @@ -28,9 +31,6 @@ pub use num_traits::{ checked_pow, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, One, Signed, Unsigned, Zero, }; -use sp_std::ops::{ - Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, Shr, Sub, SubAssign, -}; use crate::MultiplyRational; @@ -262,7 +262,7 @@ pub trait Saturating { Self: One, { let mut o = Self::one(); - sp_std::mem::swap(&mut o, self); + core::mem::swap(&mut o, self); *self = o.saturating_add(One::one()); } @@ -272,7 +272,7 @@ pub trait Saturating { Self: One, { let mut o = Self::one(); - sp_std::mem::swap(&mut o, self); + core::mem::swap(&mut o, self); *self = o.saturating_sub(One::one()); } @@ -282,7 +282,7 @@ pub trait Saturating { Self: One, { let mut o = Self::one(); - sp_std::mem::swap(&mut o, self); + core::mem::swap(&mut o, self); *self = o.saturating_add(amount); } @@ -292,7 +292,7 @@ pub trait Saturating { Self: One, { let mut o = Self::one(); - sp_std::mem::swap(&mut o, self); + core::mem::swap(&mut o, self); *self = o.saturating_sub(amount); } } @@ -949,7 +949,7 @@ mod ensure { } } - impl sp_std::ops::Mul for Signum { + impl core::ops::Mul for Signum { type Output = Self; fn mul(self, rhs: Self) -> Self { diff --git a/substrate/primitives/blockchain/Cargo.toml b/substrate/primitives/blockchain/Cargo.toml index 176e9ed6dd28f1fc55b7b237746325071d8a9e64..9d13d627eebc36dcc5bdce3efd073a7dc29012a0 100644 --- a/substrate/primitives/blockchain/Cargo.toml +++ b/substrate/primitives/blockchain/Cargo.toml @@ -19,10 +19,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } parking_lot = "0.12.1" schnellru = "0.2.1" -thiserror = "1.0.48" +thiserror = { workspace = true } sp-api = { path = "../api" } sp-consensus = { path = "../consensus/common" } sp-database = { path = "../database" } diff --git a/substrate/primitives/consensus/aura/src/inherents.rs b/substrate/primitives/consensus/aura/src/inherents.rs index 1ef25feb0ad62ef71a96c9bd17a6dc3ac8385cb8..733081dd790a1e94acb8c033192727df74bfc26c 100644 --- a/substrate/primitives/consensus/aura/src/inherents.rs +++ b/substrate/primitives/consensus/aura/src/inherents.rs @@ -69,7 +69,7 @@ impl InherentDataProvider { } #[cfg(feature = "std")] -impl sp_std::ops::Deref for InherentDataProvider { +impl core::ops::Deref for InherentDataProvider { type Target = InherentType; fn deref(&self) -> &Self::Target { diff --git a/substrate/primitives/consensus/babe/Cargo.toml b/substrate/primitives/consensus/babe/Cargo.toml index 9409b44ef62d38b322fca70f9abc95d72e020e58..8b3006f79a7ffc87a69a7eb2921f4b4f0533d1a1 100644 --- a/substrate/primitives/consensus/babe/Cargo.toml +++ b/substrate/primitives/consensus/babe/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { version = "0.1.74", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false } sp-consensus-slots = { path = "../slots", default-features = false } diff --git a/substrate/primitives/consensus/babe/src/inherents.rs b/substrate/primitives/consensus/babe/src/inherents.rs index b01bd1c9221f2043a799f93450280ac7b280401b..909769f3031be658e758087c4323ed7ec80fdcce 100644 --- a/substrate/primitives/consensus/babe/src/inherents.rs +++ b/substrate/primitives/consensus/babe/src/inherents.rs @@ -17,8 +17,8 @@ //! Inherents for BABE +use core::result::Result; use sp_inherents::{Error, InherentData, InherentIdentifier}; -use sp_std::result::Result; /// The BABE inherent identifier. pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot"; @@ -75,7 +75,7 @@ impl InherentDataProvider { } #[cfg(feature = "std")] -impl sp_std::ops::Deref for InherentDataProvider { +impl core::ops::Deref for InherentDataProvider { type Target = InherentType; fn deref(&self) -> &Self::Target { diff --git a/substrate/primitives/consensus/babe/src/lib.rs b/substrate/primitives/consensus/babe/src/lib.rs index d6b2cdd55e0daddf7e49a132d48bbc8c1741736a..ff0b4568226ef129305d6ab516d6b7032c54c2a0 100644 --- a/substrate/primitives/consensus/babe/src/lib.rs +++ b/substrate/primitives/consensus/babe/src/lib.rs @@ -256,6 +256,12 @@ pub struct BabeEpochConfiguration { pub allowed_slots: AllowedSlots, } +impl Default for BabeEpochConfiguration { + fn default() -> Self { + Self { c: (1, 4), allowed_slots: AllowedSlots::PrimaryAndSecondaryVRFSlots } + } +} + /// Verifies the equivocation proof by making sure that: both headers have /// different hashes, are targetting the same slot, and have valid signatures by /// the same authority. diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 6953fdad53cbdffd0723792dec198b818813b7e4..8ab817d52ef93469959c4de17a459a319b07b4e0 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false } sp-core = { path = "../../core", default-features = false } @@ -25,9 +25,10 @@ sp-crypto-hashing = { path = "../../crypto/hashing", default-features = false } sp-io = { path = "../../io", default-features = false } sp-mmr-primitives = { path = "../../merkle-mountain-range", default-features = false } sp-runtime = { path = "../../runtime", default-features = false } +sp-keystore = { path = "../../keystore", default-features = false } sp-std = { path = "../../std", default-features = false } strum = { version = "0.24.1", features = ["derive"], default-features = false } -lazy_static = "1.4.0" +lazy_static = { version = "1.4.0", optional = true } [dev-dependencies] array-bytes = "6.1" @@ -37,6 +38,7 @@ w3f-bls = { version = "0.1.3", features = ["std"] } default = ["std"] std = [ "codec/std", + "dep:lazy_static", "scale-info/std", "serde/std", "sp-api/std", @@ -44,6 +46,7 @@ std = [ "sp-core/std", "sp-crypto-hashing/std", "sp-io/std", + "sp-keystore/std", "sp-mmr-primitives/std", "sp-runtime/std", "sp-std/std", diff --git a/substrate/primitives/consensus/beefy/src/commitment.rs b/substrate/primitives/consensus/beefy/src/commitment.rs index 37be1a4f6fc3e20977e6ee1beb90cf2cf8e67874..335c6b604f044872ca5dc20319d0e2de94313606 100644 --- a/substrate/primitives/consensus/beefy/src/commitment.rs +++ b/substrate/primitives/consensus/beefy/src/commitment.rs @@ -97,6 +97,19 @@ pub struct SignedCommitment { pub signatures: Vec>, } +impl sp_std::fmt::Display + for SignedCommitment +{ + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + let signatures_count = self.signatures.iter().filter(|s| s.is_some()).count(); + write!( + f, + "SignedCommitment(commitment: {:?}, signatures_count: {})", + self.commitment, signatures_count + ) + } +} + impl SignedCommitment { /// Return the number of collected signatures. pub fn no_of_signatures(&self) -> usize { @@ -241,6 +254,14 @@ pub enum VersionedFinalityProof { V1(SignedCommitment), } +impl sp_std::fmt::Display for VersionedFinalityProof { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + match self { + VersionedFinalityProof::V1(sc) => write!(f, "VersionedFinalityProof::V1({})", sc), + } + } +} + impl From> for VersionedFinalityProof { fn from(commitment: SignedCommitment) -> Self { VersionedFinalityProof::V1(commitment) @@ -398,7 +419,7 @@ mod tests { assert_eq!( encoded, array_bytes::hex2bytes_unchecked( - "046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01667603fc041cf9d7147d22bf54b15e5778893d6986b71a929747befd3b4d233fbe668bc480e8865116b94db46ca25a01e03c71955f2582604e415da68f2c3c406b9d5f4ad416230ec5453f05ac16a50d8d0923dfb0413cc956ae3fa6334465bd1f2cacec8e9cd606438390fe2a29dc052d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00df61d3b2be0963eb6caa243cc505d327aec73e1bb7ffe9a14b1354b0c406792ac6d6f47c06987c15dec9993f43eefa001d866fe0850d986702c414840f0d9ec0fdc04832ef91ae37c8d49e2f573ca50cb37f152801d489a19395cb04e5fc8f2ab6954b58a3bcc40ef9b6409d2ff7ef07" + "046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba015dd1c9b2237e54baa93d232cdf83a430b58a5efbc2f86ca1bab173a315ff6f15bef161425750c028055e9a23947b73002889a8b22168628438875a8ef25d76db998a80187b50719471286f054f3b3809b77a0cd87d7fe9c1a9d5d562683e25a70610f0804e92340549a43a7159b77b0c2d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a001074884b6998c82331bd57ffa0a02cbfd02483c765b9216eab6a1fc119206236bf7971be68acaebff7400edee943240006a6096c9cfa65e9eb4e67f025c27112d14b4574fb208c439500f45cf3a8060f6cf009044f3141cce0364a7c2710a19b1bdf4abf27f86e5e3db08bddd35a7d12" ) ); } diff --git a/substrate/primitives/consensus/beefy/src/lib.rs b/substrate/primitives/consensus/beefy/src/lib.rs index 68eeeb3c680071dff08aa0aac9de3bf6e62c140e..1c3801e3a506bc9c04976713e1fae9cf02511a3d 100644 --- a/substrate/primitives/consensus/beefy/src/lib.rs +++ b/substrate/primitives/consensus/beefy/src/lib.rs @@ -32,20 +32,22 @@ //! while GRANDPA uses `ed25519`. mod commitment; -pub mod mmr; mod payload; -#[cfg(feature = "std")] -mod test_utils; + +pub mod mmr; pub mod witness; +/// Test utilities +#[cfg(feature = "std")] +pub mod test_utils; + pub use commitment::{Commitment, SignedCommitment, VersionedFinalityProof}; pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider}; -#[cfg(feature = "std")] -pub use test_utils::*; use codec::{Codec, Decode, Encode}; +use core::fmt::{Debug, Display}; use scale_info::TypeInfo; -use sp_application_crypto::RuntimeAppPublic; +use sp_application_crypto::{AppCrypto, AppPublic, ByteArray, RuntimeAppPublic}; use sp_core::H256; use sp_runtime::traits::{Hash, Keccak256, NumberFor}; use sp_std::prelude::*; @@ -63,6 +65,25 @@ pub trait BeefyAuthorityId: RuntimeAppPublic { fn verify(&self, signature: &::Signature, msg: &[u8]) -> bool; } +/// Hasher used for BEEFY signatures. +pub type BeefySignatureHasher = sp_runtime::traits::Keccak256; + +/// A trait bound which lists all traits which are required to be implemented by +/// a BEEFY AuthorityId type in order to be able to be used in BEEFY Keystore +pub trait AuthorityIdBound: + Codec + + Debug + + Clone + + AsRef<[u8]> + + ByteArray + + AppPublic + + AppCrypto + + RuntimeAppPublic + + Display + + BeefyAuthorityId +{ +} + /// BEEFY cryptographic types for ECDSA crypto /// /// This module basically introduces four crypto types: @@ -74,7 +95,7 @@ pub trait BeefyAuthorityId: RuntimeAppPublic { /// Your code should use the above types as concrete types for all crypto related /// functionality. pub mod ecdsa_crypto { - use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; + use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; use sp_application_crypto::{app_crypto, ecdsa}; use sp_core::crypto::Wraps; @@ -101,6 +122,7 @@ pub mod ecdsa_crypto { } } } + impl AuthorityIdBound for AuthorityId {} } /// BEEFY cryptographic types for BLS crypto @@ -116,7 +138,7 @@ pub mod ecdsa_crypto { #[cfg(feature = "bls-experimental")] pub mod bls_crypto { - use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; + use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; use sp_application_crypto::{app_crypto, bls377}; use sp_core::{bls377::Pair as BlsPair, crypto::Wraps, Pair as _}; @@ -134,13 +156,14 @@ pub mod bls_crypto { { fn verify(&self, signature: &::Signature, msg: &[u8]) -> bool { // `w3f-bls` library uses IETF hashing standard and as such does not expose - // a choice of hash to field function. + // a choice of hash-to-field function. // We are directly calling into the library to avoid introducing new host call. // and because BeefyAuthorityId::verify is being called in the runtime so we don't have BlsPair::verify(signature.as_inner_ref(), msg, self.as_inner_ref()) } } + impl AuthorityIdBound for AuthorityId {} } /// BEEFY cryptographic types for (ECDSA,BLS) crypto pair @@ -155,7 +178,7 @@ pub mod bls_crypto { /// functionality. #[cfg(feature = "bls-experimental")] pub mod ecdsa_bls_crypto { - use super::{BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; + use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE}; use sp_application_crypto::{app_crypto, ecdsa_bls377}; use sp_core::{crypto::Wraps, ecdsa_bls377::Pair as EcdsaBlsPair}; @@ -187,6 +210,8 @@ pub mod ecdsa_bls_crypto { ) } } + + impl AuthorityIdBound for AuthorityId {} } /// The `ConsensusEngineId` of BEEFY. diff --git a/substrate/primitives/consensus/beefy/src/test_utils.rs b/substrate/primitives/consensus/beefy/src/test_utils.rs index a6e65e5bff0b0d6bc75d93ea3d5fc7b49741287f..ec13c9c690046ae795fe50af3bfc775721dfb6ee 100644 --- a/substrate/primitives/consensus/beefy/src/test_utils.rs +++ b/substrate/primitives/consensus/beefy/src/test_utils.rs @@ -15,18 +15,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg(feature = "std")] +#[cfg(feature = "bls-experimental")] +use crate::ecdsa_bls_crypto; +use crate::{ + ecdsa_crypto, AuthorityIdBound, BeefySignatureHasher, Commitment, EquivocationProof, Payload, + ValidatorSetId, VoteMessage, +}; +use sp_application_crypto::{AppCrypto, AppPair, RuntimeAppPublic, Wraps}; +use sp_core::{ecdsa, Pair}; +use sp_runtime::traits::Hash; -use crate::{ecdsa_crypto, Commitment, EquivocationProof, Payload, ValidatorSetId, VoteMessage}; use codec::Encode; -use sp_core::{ecdsa, Pair}; -use std::collections::HashMap; +use std::{collections::HashMap, marker::PhantomData}; use strum::IntoEnumIterator; /// Set of test accounts using [`crate::ecdsa_crypto`] types. #[allow(missing_docs)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)] -pub enum Keyring { +pub enum Keyring { Alice, Bob, Charlie, @@ -35,71 +41,110 @@ pub enum Keyring { Ferdie, One, Two, + _Marker(PhantomData), +} + +/// Trait representing BEEFY specific generation and signing behavior of authority id +/// +/// Accepts custom hashing fn for the message and custom convertor fn for the signer. +pub trait BeefySignerAuthority: AppPair { + /// Generate and return signature for `message` using custom hashing `MsgHash` + fn sign_with_hasher(&self, message: &[u8]) -> ::Signature; +} + +impl BeefySignerAuthority for ::Pair +where + MsgHash: Hash, + ::Output: Into<[u8; 32]>, +{ + fn sign_with_hasher(&self, message: &[u8]) -> ::Signature { + let hashed_message = ::hash(message).into(); + self.as_inner_ref().sign_prehashed(&hashed_message).into() + } +} + +#[cfg(feature = "bls-experimental")] +impl BeefySignerAuthority for ::Pair +where + MsgHash: Hash, + ::Output: Into<[u8; 32]>, +{ + fn sign_with_hasher(&self, message: &[u8]) -> ::Signature { + self.as_inner_ref().sign_with_hasher::(&message).into() + } } -impl Keyring { +/// Implement Keyring functionalities generically over AuthorityId +impl Keyring +where + AuthorityId: AuthorityIdBound + From<<::Pair as AppCrypto>::Public>, + ::Pair: BeefySignerAuthority, + ::Signature: + Send + Sync + From<<::Pair as AppCrypto>::Signature>, +{ /// Sign `msg`. - pub fn sign(self, msg: &[u8]) -> ecdsa_crypto::Signature { - // todo: use custom signature hashing type - let msg = sp_crypto_hashing::keccak_256(msg); - ecdsa::Pair::from(self).sign_prehashed(&msg).into() + pub fn sign(&self, msg: &[u8]) -> ::Signature { + let key_pair: ::Pair = self.pair(); + key_pair.sign_with_hasher(&msg).into() } /// Return key pair. - pub fn pair(self) -> ecdsa_crypto::Pair { - ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into() + pub fn pair(&self) -> ::Pair { + ::Pair::from_string(self.to_seed().as_str(), None) + .unwrap() + .into() } /// Return public key. - pub fn public(self) -> ecdsa_crypto::Public { - self.pair().public() + pub fn public(&self) -> AuthorityId { + self.pair().public().into() } /// Return seed string. - pub fn to_seed(self) -> String { + pub fn to_seed(&self) -> String { format!("//{}", self) } /// Get Keyring from public key. - pub fn from_public(who: &ecdsa_crypto::Public) -> Option { - Self::iter().find(|&k| &ecdsa_crypto::Public::from(k) == who) + pub fn from_public(who: &AuthorityId) -> Option> { + Self::iter().find(|k| k.public() == *who) } } lazy_static::lazy_static! { - static ref PRIVATE_KEYS: HashMap = - Keyring::iter().map(|i| (i, i.pair())).collect(); - static ref PUBLIC_KEYS: HashMap = - PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect(); + static ref PRIVATE_KEYS: HashMap, ecdsa_crypto::Pair> = + Keyring::iter().map(|i| (i.clone(), i.pair())).collect(); + static ref PUBLIC_KEYS: HashMap, ecdsa_crypto::Public> = + PRIVATE_KEYS.iter().map(|(name, pair)| (name.clone(), sp_application_crypto::Pair::public(pair))).collect(); } -impl From for ecdsa_crypto::Pair { - fn from(k: Keyring) -> Self { +impl From> for ecdsa_crypto::Pair { + fn from(k: Keyring) -> Self { k.pair() } } -impl From for ecdsa::Pair { - fn from(k: Keyring) -> Self { +impl From> for ecdsa::Pair { + fn from(k: Keyring) -> Self { k.pair().into() } } -impl From for ecdsa_crypto::Public { - fn from(k: Keyring) -> Self { +impl From> for ecdsa_crypto::Public { + fn from(k: Keyring) -> Self { (*PUBLIC_KEYS).get(&k).cloned().unwrap() } } /// Create a new `EquivocationProof` based on given arguments. pub fn generate_equivocation_proof( - vote1: (u64, Payload, ValidatorSetId, &Keyring), - vote2: (u64, Payload, ValidatorSetId, &Keyring), + vote1: (u64, Payload, ValidatorSetId, &Keyring), + vote2: (u64, Payload, ValidatorSetId, &Keyring), ) -> EquivocationProof { let signed_vote = |block_number: u64, payload: Payload, validator_set_id: ValidatorSetId, - keyring: &Keyring| { + keyring: &Keyring| { let commitment = Commitment { validator_set_id, block_number, payload }; let signature = keyring.sign(&commitment.encode()); VoteMessage { commitment, id: keyring.public(), signature } diff --git a/substrate/primitives/consensus/common/Cargo.toml b/substrate/primitives/consensus/common/Cargo.toml index 00c2fca5e22f05cb5fd0f00880ca346ba012dd24..048e31b0265f57f0404b3777897a27f42fe3de68 100644 --- a/substrate/primitives/consensus/common/Cargo.toml +++ b/substrate/primitives/consensus/common/Cargo.toml @@ -19,8 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.74" futures = { version = "0.3.21", features = ["thread-pool"] } -log = "0.4.17" -thiserror = "1.0.48" +log = { workspace = true, default-features = true } +thiserror = { workspace = true } sp-core = { path = "../../core" } sp-inherents = { path = "../../inherents" } sp-runtime = { path = "../../runtime" } diff --git a/substrate/primitives/consensus/grandpa/Cargo.toml b/substrate/primitives/consensus/grandpa/Cargo.toml index de02b1890703c22265c8f2e71a43a473d926b02a..b06208a4308b355031adde70de72e0e136debda5 100644 --- a/substrate/primitives/consensus/grandpa/Cargo.toml +++ b/substrate/primitives/consensus/grandpa/Cargo.toml @@ -19,9 +19,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } grandpa = { package = "finality-grandpa", version = "0.16.2", default-features = false, features = ["derive-codec"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false, optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false } sp-core = { path = "../../core", default-features = false } diff --git a/substrate/primitives/consensus/sassafras/Cargo.toml b/substrate/primitives/consensus/sassafras/Cargo.toml index 6d44bc6c5a8fe9b879962e1ddb102b5638d9dc1b..b707ad18b5b9c6d4649a31a0c8f6a4dd4eeaf1f8 100644 --- a/substrate/primitives/consensus/sassafras/Cargo.toml +++ b/substrate/primitives/consensus/sassafras/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] scale-codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true } sp-api = { path = "../../api", default-features = false } sp-application-crypto = { path = "../../application-crypto", default-features = false, features = ["bandersnatch-experimental"] } sp-consensus-slots = { path = "../slots", default-features = false } diff --git a/substrate/primitives/consensus/slots/Cargo.toml b/substrate/primitives/consensus/slots/Cargo.toml index fadcd1215eee8f4780457ec50052413512812af0..8372b2b04a6b662cd76a07b6d7245ba9ba46b251 100644 --- a/substrate/primitives/consensus/slots/Cargo.toml +++ b/substrate/primitives/consensus/slots/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-std = { path = "../../std", default-features = false } sp-timestamp = { path = "../../timestamp", default-features = false } diff --git a/substrate/primitives/consensus/slots/src/lib.rs b/substrate/primitives/consensus/slots/src/lib.rs index 23cb7f7365adaa431e84c268c3210f4c6dfa42c8..eb3b3d3a449f2f8279f7183446941c4dedd2610a 100644 --- a/substrate/primitives/consensus/slots/src/lib.rs +++ b/substrate/primitives/consensus/slots/src/lib.rs @@ -155,9 +155,9 @@ impl SlotDuration { #[cfg(feature = "std")] impl SlotDuration { - /// Returns `self` as [`sp_std::time::Duration`]. - pub const fn as_duration(&self) -> sp_std::time::Duration { - sp_std::time::Duration::from_millis(self.0) + /// Returns `self` as [`core::time::Duration`]. + pub const fn as_duration(&self) -> core::time::Duration { + core::time::Duration::from_millis(self.0) } } diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index f5914049f41a6c5eefe0b4ebd362d7d762c2e101..8fcabfeb238459940f58357a9b6f803535c9a177 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -18,8 +18,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } -serde = { version = "1.0.195", optional = true, default-features = false, features = ["alloc", "derive"] } +log = { workspace = true } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } bounded-collections = { version = "0.2.0", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info"] } impl-serde = { version = "0.4.0", default-features = false, optional = true } @@ -39,7 +39,7 @@ sp-storage = { path = "../storage", default-features = false } sp-externalities = { path = "../externalities", optional = true } futures = { version = "0.3.21", optional = true } dyn-clonable = { version = "0.9.0", optional = true } -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } tracing = { version = "0.1.29", optional = true } bitflags = "1.3" paste = "1.0.7" @@ -63,7 +63,7 @@ bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "e9782f9", [dev-dependencies] criterion = "0.4.0" -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } lazy_static = "1.4.0" regex = "1.6.0" diff --git a/substrate/primitives/core/src/bandersnatch.rs b/substrate/primitives/core/src/bandersnatch.rs index 96b0ff19e561a41a43584142e62f8bc6d8e886b1..61e7162544a602ba2c5577807cfbc94d143dad22 100644 --- a/substrate/primitives/core/src/bandersnatch.rs +++ b/substrate/primitives/core/src/bandersnatch.rs @@ -985,6 +985,19 @@ mod tests { assert!(res.is_err()); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + } + #[test] fn sign_verify() { let pair = Pair::from_seed(DEV_SEED); diff --git a/substrate/primitives/core/src/bls.rs b/substrate/primitives/core/src/bls.rs index 452c6372d16b3b2dccf60ac0e7bdde01f24f8cc1..0c84d0ba8e6c0fd295690ca5fddf48440f083379 100644 --- a/substrate/primitives/core/src/bls.rs +++ b/substrate/primitives/core/src/bls.rs @@ -15,7 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Simple BLS (Boneh–Lynn–Shacham) Signature API. +//! BLS (Boneh–Lynn–Shacham) Signature along with efficiently verifiable Chaum-Pedersen proof API. +//! Signatures are implemented according to +//! [Efficient Aggregatable BLS Signatures with Chaum-Pedersen Proofs](https://eprint.iacr.org/2022/1611) +//! Hash-to-BLS-curve is using Simplified SWU for AB == 0 +//! [RFC 9380](https://datatracker.ietf.org/doc/rfc9380/) Sect 6.6.3. +//! Chaum-Pedersen proof uses the same hash-to-field specified in RFC 9380 for the field of the BLS +//! curve. #[cfg(feature = "serde")] use crate::crypto::Ss58Codec; @@ -452,11 +458,12 @@ impl TraitPair for Pair { fn derive>( &self, path: Iter, - _seed: Option, + seed: Option, ) -> Result<(Self, Option), DeriveError> { - let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] = self.0.secret.to_bytes().try_into().expect( - "Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size; qed", - ); + let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] = + seed.unwrap_or(self.0.secret.to_bytes().try_into().expect( + "Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size; qed", + )); for j in path { match j { DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), @@ -544,7 +551,7 @@ mod test { "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", ); let pair = Pair::from_seed(&seed); - // we are using hash to field so this is not going to work + // we are using hash-to-field so this is not going to work // assert_eq!(pair.seed(), seed); let path = vec![DeriveJunction::Hard([0u8; 32])]; let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; @@ -588,12 +595,12 @@ mod test { assert_eq!( public, Public::unchecked_from(array_bytes::hex2array_unchecked( - "6dc6be608fab3c6bd894a606be86db346cc170db85c733853a371f3db54ae1b12052c0888d472760c81b537572a26f00db865e5963aef8634f9917571c51b538b564b2a9ceda938c8b930969ee3b832448e08e33a79e9ddd28af419a3ce45300f5dbc768b067781f44f3fe05a19e6b07b1c4196151ec3f8ea37e4f89a8963030d2101e931276bb9ebe1f20102239d780" + "7a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400" )) ); let message = b""; let signature = - array_bytes::hex2array_unchecked("bbb395bbdee1a35930912034f5fde3b36df2835a0536c865501b0675776a1d5931a3bea2e66eff73b2546c6af2061a8019223e4ebbbed661b2538e0f5823f2c708eb89c406beca8fcb53a5c13dbc7c0c42e4cf2be2942bba96ea29297915a06bd2b1b979c0e2ac8fd4ec684a6b5d110c" + array_bytes::hex2array_unchecked("d1e3013161991e142d8751017d4996209c2ff8a9ee160f373733eda3b4b785ba6edce9f45f87104bbe07aa6aa6eb2780aa705efb2c13d3b317d6409d159d23bdc7cdd5c2a832d1551cf49d811d49c901495e527dbd532e3a462335ce2686009104aba7bc11c5b22be78f3198d2727a0b" ); let expected_signature = Signature::unchecked_from(signature); println!("signature is {:?}", pair.sign(&message[..])); @@ -647,12 +654,30 @@ mod test { assert_eq!(pair1.public(), pair2.public()); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn password_does_something() { let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec()); } #[test] diff --git a/substrate/primitives/core/src/crypto.rs b/substrate/primitives/core/src/crypto.rs index d8436d2a96cb1a380bc2534a94e50fa5940d4fc5..2a8be2a2ba8503d7dca2d324e405f6a1af840247 100644 --- a/substrate/primitives/core/src/crypto.rs +++ b/substrate/primitives/core/src/crypto.rs @@ -773,6 +773,8 @@ mod dummy { /// Similarly an empty password (ending the `SURI` with `///`) is perfectly valid and will /// generally be equivalent to no password at all. /// +/// The `password` is used as salt when generating the seed from the BIP-39 key phrase. +/// /// # Example /// /// Parse [`DEV_PHRASE`] secret uri with junction: diff --git a/substrate/primitives/core/src/ecdsa.rs b/substrate/primitives/core/src/ecdsa.rs index 1b63db7af7f2b73f41766fc31061e7333af922a2..f172b3a7d02c7a1adb1ff2fc37933d3cf47a165f 100644 --- a/substrate/primitives/core/src/ecdsa.rs +++ b/substrate/primitives/core/src/ecdsa.rs @@ -644,12 +644,29 @@ mod test { assert_eq!(pair1.public(), pair2.public()); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.secret, repair_seed.secret); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.secret, repair_phrase.secret); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.secret, repair_string.secret); + } + #[test] fn password_does_something() { let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.secret, pair2.secret); } #[test] diff --git a/substrate/primitives/core/src/ed25519.rs b/substrate/primitives/core/src/ed25519.rs index aa0d77510bd81efdd9186e730842622f65620558..60ebd93e12d43d6d331c6758508f843145031e74 100644 --- a/substrate/primitives/core/src/ed25519.rs +++ b/substrate/primitives/core/src/ed25519.rs @@ -501,6 +501,22 @@ mod test { ); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn test_vector_should_work() { let pair = Pair::from_seed(&array_bytes::hex2array_unchecked( @@ -590,6 +606,7 @@ mod test { let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec()); } #[test] diff --git a/substrate/primitives/core/src/paired_crypto.rs b/substrate/primitives/core/src/paired_crypto.rs index 960b8469249e22b10352545f26d3940df3da2e77..20b32c339bd763d4847f4d36626635a4226f80d9 100644 --- a/substrate/primitives/core/src/paired_crypto.rs +++ b/substrate/primitives/core/src/paired_crypto.rs @@ -460,10 +460,11 @@ where path: Iter, seed: Option, ) -> Result<(Self, Option), DeriveError> { - let path: Vec<_> = path.collect(); + let left_path: Vec<_> = path.collect(); + let right_path: Vec<_> = left_path.clone(); - let left = self.left.derive(path.iter().cloned(), seed.map(|s| s.into()))?; - let right = self.right.derive(path.into_iter(), seed.map(|s| s.into()))?; + let left = self.left.derive(left_path.into_iter(), seed.map(|s| s.into()))?; + let right = self.right.derive(right_path.into_iter(), seed.map(|s| s.into()))?; let seed = match (left.1, right.1) { (Some(l), Some(r)) if l.as_ref() == r.as_ref() => Some(l.into()), @@ -542,13 +543,30 @@ mod test { ); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn seed_and_derive_should_work() { let seed_for_right_and_left: [u8; SECURE_SEED_LEN] = array_bytes::hex2array_unchecked( "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", ); let pair = Pair::from_seed(&seed_for_right_and_left); - // we are using hash to field so this is not going to work + // we are using hash-to-field so this is not going to work // assert_eq!(pair.seed(), seed); let path = vec![DeriveJunction::Hard([0u8; 32])]; let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; @@ -599,13 +617,13 @@ mod test { assert_eq!( public, Public::unchecked_from( - array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd916dc6be608fab3c6bd894a606be86db346cc170db85c733853a371f3db54ae1b12052c0888d472760c81b537572a26f00db865e5963aef8634f9917571c51b538b564b2a9ceda938c8b930969ee3b832448e08e33a79e9ddd28af419a3ce45300f5dbc768b067781f44f3fe05a19e6b07b1c4196151ec3f8ea37e4f89a8963030d2101e931276bb9ebe1f20102239d780" + array_bytes::hex2array_unchecked("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd917a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400" ), ), ); let message = b""; let signature = - array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00bbb395bbdee1a35930912034f5fde3b36df2835a0536c865501b0675776a1d5931a3bea2e66eff73b2546c6af2061a8019223e4ebbbed661b2538e0f5823f2c708eb89c406beca8fcb53a5c13dbc7c0c42e4cf2be2942bba96ea29297915a06bd2b1b979c0e2ac8fd4ec684a6b5d110c" + array_bytes::hex2array_unchecked("3dde91174bd9359027be59a428b8146513df80a2a3c7eda2194f64de04a69ab97b753169e94db6ffd50921a2668a48b94ca11e3d32c1ff19cfe88890aa7e8f3c00d1e3013161991e142d8751017d4996209c2ff8a9ee160f373733eda3b4b785ba6edce9f45f87104bbe07aa6aa6eb2780aa705efb2c13d3b317d6409d159d23bdc7cdd5c2a832d1551cf49d811d49c901495e527dbd532e3a462335ce2686009104aba7bc11c5b22be78f3198d2727a0b" ); let signature = Signature::unchecked_from(signature); assert!(pair.sign(&message[..]) == signature); @@ -664,6 +682,7 @@ mod test { let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); assert_ne!(pair1.public(), pair2.public()); + assert_ne!(pair1.to_raw_vec(), pair2.to_raw_vec()); } #[test] diff --git a/substrate/primitives/core/src/sr25519.rs b/substrate/primitives/core/src/sr25519.rs index b821055e2c56713067bf096899c2b10b6155c877..7c02afc3cd5ff8fe378613d88c088c95ceb805c8 100644 --- a/substrate/primitives/core/src/sr25519.rs +++ b/substrate/primitives/core/src/sr25519.rs @@ -970,6 +970,22 @@ mod tests { assert!(Pair::verify(&signature, &message[..], &public)); } + #[test] + fn generate_with_phrase_should_be_recoverable_with_from_string() { + let (pair, phrase, seed) = Pair::generate_with_phrase(None); + let repair_seed = Pair::from_seed_slice(seed.as_ref()).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_seed.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let (repair_phrase, reseed) = + Pair::from_phrase(phrase.as_ref(), None).expect("seed slice is valid"); + assert_eq!(seed, reseed); + assert_eq!(pair.public(), repair_phrase.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + let repair_string = Pair::from_string(phrase.as_str(), None).expect("seed slice is valid"); + assert_eq!(pair.public(), repair_string.public()); + assert_eq!(pair.to_raw_vec(), repair_seed.to_raw_vec()); + } + #[test] fn generated_pair_should_work() { let (pair, _) = Pair::generate(); diff --git a/substrate/primitives/core/src/testing.rs b/substrate/primitives/core/src/testing.rs index 947dcc387fc79605e4c51d9c28e88c2664dd28c7..c26e23d442f1f584ed888de7e65b5b81a230f70e 100644 --- a/substrate/primitives/core/src/testing.rs +++ b/substrate/primitives/core/src/testing.rs @@ -89,7 +89,7 @@ macro_rules! wasm_export_functions { &[0u8; 0] } else { unsafe { - $crate::sp_std::slice::from_raw_parts(input_data, input_len) + ::core::slice::from_raw_parts(input_data, input_len) } }; @@ -117,7 +117,7 @@ macro_rules! wasm_export_functions { &[0u8; 0] } else { unsafe { - $crate::sp_std::slice::from_raw_parts(input_data, input_len) + ::core::slice::from_raw_parts(input_data, input_len) } }; diff --git a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml index 179b70048a754799fd3b3693f934a0807cb872a6..f244b02ca101670df10f5aea1a1482cb86cfdaed 100644 --- a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml +++ b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml @@ -19,6 +19,6 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.28" -syn = { version = "2.0.48", features = ["full", "parsing"] } +quote = { workspace = true } +syn = { features = ["full", "parsing"], workspace = true } sp-crypto-hashing = { path = "..", default-features = false } diff --git a/substrate/primitives/debug-derive/Cargo.toml b/substrate/primitives/debug-derive/Cargo.toml index 2a18f505fcbc8ad1f61449a20a58b1d8bc2aaa94..debf964aa3dfdf7cebd23e0f1d24e74b0d880ec0 100644 --- a/substrate/primitives/debug-derive/Cargo.toml +++ b/substrate/primitives/debug-derive/Cargo.toml @@ -19,8 +19,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.28" -syn = "2.0.48" +quote = { workspace = true } +syn = { workspace = true } proc-macro2 = "1.0.56" [features] diff --git a/substrate/primitives/externalities/src/extensions.rs b/substrate/primitives/externalities/src/extensions.rs index 282e6ea914a84caae6a1c63af8d9ff0e125176fc..d99dfe6cf530ae29718a1cf0a24a5a6afa851894 100644 --- a/substrate/primitives/externalities/src/extensions.rs +++ b/substrate/primitives/externalities/src/extensions.rs @@ -78,16 +78,16 @@ macro_rules! decl_extension { $vis struct $ext_name (pub $inner); impl $crate::Extension for $ext_name { - fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + fn as_mut_any(&mut self) -> &mut dyn core::any::Any { self } - fn type_id(&self) -> std::any::TypeId { - std::any::Any::type_id(self) + fn type_id(&self) -> core::any::TypeId { + core::any::Any::type_id(self) } } - impl std::ops::Deref for $ext_name { + impl core::ops::Deref for $ext_name { type Target = $inner; fn deref(&self) -> &Self::Target { @@ -95,7 +95,7 @@ macro_rules! decl_extension { } } - impl std::ops::DerefMut for $ext_name { + impl core::ops::DerefMut for $ext_name { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } @@ -115,12 +115,12 @@ macro_rules! decl_extension { $vis struct $ext_name; impl $crate::Extension for $ext_name { - fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + fn as_mut_any(&mut self) -> &mut dyn core::any::Any { self } - fn type_id(&self) -> std::any::TypeId { - std::any::Any::type_id(self) + fn type_id(&self) -> core::any::TypeId { + core::any::Any::type_id(self) } } } diff --git a/substrate/primitives/genesis-builder/Cargo.toml b/substrate/primitives/genesis-builder/Cargo.toml index 1967e9ff8856eed3f930ef50b027dd68c41ca870..bf12433c5f4048c9ad4b6dca688ffa6ef0cd3a8e 100644 --- a/substrate/primitives/genesis-builder/Cargo.toml +++ b/substrate/primitives/genesis-builder/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] sp-api = { path = "../api", default-features = false } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc", "arbitrary_precision"] } +serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/inherents/Cargo.toml b/substrate/primitives/inherents/Cargo.toml index 2a9d205e16642d3565a941f0a2969fadc43895cb..bfb1d7733471fa7fd9f0a13c14d337e34188065d 100644 --- a/substrate/primitives/inherents/Cargo.toml +++ b/substrate/primitives/inherents/Cargo.toml @@ -21,7 +21,7 @@ async-trait = { version = "0.1.74", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } sp-runtime = { path = "../runtime", default-features = false, optional = true } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index d2d56b831532a4fdbf6e3ca70cf52820b2dc42fc..c78def9bf4429eab1e5c9822a52732ee3a3c74ca 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -30,7 +30,7 @@ sp-runtime-interface = { path = "../runtime-interface", default-features = false sp-trie = { path = "../trie", default-features = false, optional = true } sp-externalities = { path = "../externalities", default-features = false } sp-tracing = { path = "../tracing", default-features = false } -log = { version = "0.4.17", optional = true } +log = { optional = true, workspace = true, default-features = true } secp256k1 = { version = "0.28.0", features = ["global-context", "recovery"], optional = true } tracing = { version = "0.1.29", default-features = false } tracing-core = { version = "0.1.32", default-features = false } diff --git a/substrate/primitives/io/src/lib.rs b/substrate/primitives/io/src/lib.rs index 20ea56bc26c98fd8bbe190e02f2aae7e3cebb971..6d34199416a36deeb011003989f6e361d535a0e6 100644 --- a/substrate/primitives/io/src/lib.rs +++ b/substrate/primitives/io/src/lib.rs @@ -1737,20 +1737,20 @@ mod tracing_setup { pub use tracing_setup::init_tracing; -/// Allocator used by Substrate when executing the Wasm runtime. -#[cfg(all(target_arch = "wasm32", not(feature = "std")))] -struct WasmAllocator; +/// Allocator used by Substrate from within the runtime. +#[cfg(substrate_runtime)] +struct RuntimeAllocator; -#[cfg(all(target_arch = "wasm32", not(feature = "disable_allocator"), not(feature = "std")))] +#[cfg(all(not(feature = "disable_allocator"), substrate_runtime))] #[global_allocator] -static ALLOCATOR: WasmAllocator = WasmAllocator; +static ALLOCATOR: RuntimeAllocator = RuntimeAllocator; -#[cfg(all(target_arch = "wasm32", not(feature = "std")))] +#[cfg(substrate_runtime)] mod allocator_impl { use super::*; use core::alloc::{GlobalAlloc, Layout}; - unsafe impl GlobalAlloc for WasmAllocator { + unsafe impl GlobalAlloc for RuntimeAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { allocator::malloc(layout.size() as u32) } @@ -1761,8 +1761,27 @@ mod allocator_impl { } } -/// A default panic handler for WASM environment. -#[cfg(all(not(feature = "disable_panic_handler"), not(feature = "std")))] +/// Crashes the execution of the program. +/// +/// Equivalent to the WASM `unreachable` instruction, RISC-V `unimp` instruction, +/// or just the `unreachable!()` macro everywhere else. +pub fn unreachable() -> ! { + #[cfg(target_family = "wasm")] + { + core::arch::wasm32::unreachable(); + } + + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + unsafe { + core::arch::asm!("unimp", options(noreturn)); + } + + #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_family = "wasm")))] + unreachable!(); +} + +/// A default panic handler for the runtime environment. +#[cfg(all(not(feature = "disable_panic_handler"), substrate_runtime))] #[panic_handler] #[no_mangle] pub fn panic(info: &core::panic::PanicInfo) -> ! { @@ -1774,11 +1793,11 @@ pub fn panic(info: &core::panic::PanicInfo) -> ! { #[cfg(not(feature = "improved_panic_error_reporting"))] { logging::log(LogLevel::Error, "runtime", message.as_bytes()); - core::arch::wasm32::unreachable(); + unreachable(); } } -/// A default OOM handler for WASM environment. +/// A default OOM handler for the runtime environment. #[cfg(all(not(feature = "disable_oom"), enable_alloc_error_handler))] #[alloc_error_handler] pub fn oom(_: core::alloc::Layout) -> ! { @@ -1789,7 +1808,7 @@ pub fn oom(_: core::alloc::Layout) -> ! { #[cfg(not(feature = "improved_panic_error_reporting"))] { logging::log(LogLevel::Error, "runtime", b"Runtime memory exhausted. Aborting"); - core::arch::wasm32::unreachable(); + unreachable(); } } diff --git a/substrate/primitives/keystore/Cargo.toml b/substrate/primitives/keystore/Cargo.toml index 139f04beb205f5dd72c1549c680dedfe4685d63e..a34839358e18333154962f71e1957f9884a71a2f 100644 --- a/substrate/primitives/keystore/Cargo.toml +++ b/substrate/primitives/keystore/Cargo.toml @@ -17,8 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -parking_lot = { version = "0.12.1", default-features = false } -thiserror = "1.0" +parking_lot = { version = "0.12.1", default-features = false, optional = true } sp-core = { path = "../core", default-features = false } sp-externalities = { path = "../externalities", default-features = false } @@ -28,7 +27,7 @@ rand_chacha = "0.2.2" [features] default = ["std"] -std = ["codec/std", "sp-core/std", "sp-externalities/std"] +std = ["codec/std", "dep:parking_lot", "sp-core/std", "sp-externalities/std"] # This feature adds BLS crypto primitives. # It should not be used in production since the implementation and interface may still diff --git a/substrate/primitives/keystore/src/lib.rs b/substrate/primitives/keystore/src/lib.rs index 07c4e2d5fd1dc4b8cc903d34154dbc51a050426c..64f0e3ea49e8bd3a7e682ad024cd7c28fbcd3abd 100644 --- a/substrate/primitives/keystore/src/lib.rs +++ b/substrate/primitives/keystore/src/lib.rs @@ -17,6 +17,10 @@ //! Keystore traits +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + #[cfg(feature = "std")] pub mod testing; @@ -29,25 +33,35 @@ use sp_core::{ ecdsa, ed25519, sr25519, }; -use std::sync::Arc; +use alloc::{string::String, sync::Arc, vec::Vec}; /// Keystore error -#[derive(Debug, thiserror::Error)] +#[derive(Debug)] pub enum Error { /// Public key type is not supported - #[error("Key not supported: {0:?}")] KeyNotSupported(KeyTypeId), /// Validation error - #[error("Validation error: {0}")] ValidationError(String), /// Keystore unavailable - #[error("Keystore unavailable")] Unavailable, /// Programming errors - #[error("An unknown keystore error occurred: {0}")] Other(String), } +impl core::fmt::Display for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + Error::KeyNotSupported(key_type) => write!(fmt, "Key not supported: {key_type:?}"), + Error::ValidationError(error) => write!(fmt, "Validation error: {error}"), + Error::Unavailable => fmt.write_str("Keystore unavailable"), + Error::Other(error) => write!(fmt, "An unknown keystore error occurred: {error}"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + /// Something that generates, stores and provides access to secret keys. pub trait Keystore: Send + Sync { /// Returns all the sr25519 public keys for the given key type. @@ -355,6 +369,24 @@ pub trait Keystore: Send + Sync { msg: &[u8], ) -> Result, Error>; + /// Hashes the `message` using keccak256 and then signs it using ECDSA + /// algorithm. It does not affect the behavior of BLS12-377 component. It generates + /// BLS12-377 Signature according to IETF standard. + /// + /// Receives [`KeyTypeId`] and a [`ecdsa_bls377::Public`] key to be able to map + /// them to a private key that exists in the keystore. + /// + /// Returns an [`ecdsa_bls377::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> Result, Error>; + /// Insert a new secret key. fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()>; @@ -661,6 +693,16 @@ impl Keystore for Arc { (**self).ecdsa_bls377_sign(key_type, public, msg) } + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> Result, Error> { + (**self).ecdsa_bls377_sign_with_keccak256(key_type, public, msg) + } + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { (**self).insert(key_type, suri, public) } diff --git a/substrate/primitives/keystore/src/testing.rs b/substrate/primitives/keystore/src/testing.rs index 2879c458b4f68c40338b544532bdf80b9bf1d32f..e10660b126a33f23e00f5774412f65d665cc6737 100644 --- a/substrate/primitives/keystore/src/testing.rs +++ b/substrate/primitives/keystore/src/testing.rs @@ -22,7 +22,7 @@ use crate::{Error, Keystore, KeystorePtr}; #[cfg(feature = "bandersnatch-experimental")] use sp_core::bandersnatch; #[cfg(feature = "bls-experimental")] -use sp_core::{bls377, bls381, ecdsa_bls377}; +use sp_core::{bls377, bls381, ecdsa_bls377, KeccakHasher}; use sp_core::{ crypto::{ByteArray, KeyTypeId, Pair, VrfSecret}, ecdsa, ed25519, sr25519, @@ -346,6 +346,19 @@ impl Keystore for MemoryKeystore { self.sign::(key_type, public, msg) } + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak256( + &self, + key_type: KeyTypeId, + public: &ecdsa_bls377::Public, + msg: &[u8], + ) -> Result, Error> { + let sig = self + .pair::(key_type, public) + .map(|pair| pair.sign_with_hasher::(msg)); + Ok(sig) + } + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { self.keys .write() @@ -493,6 +506,38 @@ mod tests { assert!(res.is_some()); } + #[test] + #[cfg(feature = "bls-experimental")] + fn ecdsa_bls377_sign_with_keccak_works() { + use sp_core::testing::ECDSA_BLS377; + + let store = MemoryKeystore::new(); + + let suri = "//Alice"; + let pair = ecdsa_bls377::Pair::from_string(suri, None).unwrap(); + + let msg = b"this should be a normal unhashed message not "; + + // insert key, sign again + store.insert(ECDSA_BLS377, suri, pair.public().as_ref()).unwrap(); + + let res = store + .ecdsa_bls377_sign_with_keccak256(ECDSA_BLS377, &pair.public(), &msg[..]) + .unwrap(); + + assert!(res.is_some()); + + // does not verify with default out-of-the-box verification + assert!(!ecdsa_bls377::Pair::verify(&res.clone().unwrap(), &msg[..], &pair.public())); + + // should verify using keccak256 as hasher + assert!(ecdsa_bls377::Pair::verify_with_hasher::( + &res.unwrap(), + msg, + &pair.public() + )); + } + #[test] #[cfg(feature = "bandersnatch-experimental")] fn bandersnatch_vrf_sign() { diff --git a/substrate/primitives/maybe-compressed-blob/Cargo.toml b/substrate/primitives/maybe-compressed-blob/Cargo.toml index 84a5592dcaf2702497cecf6e26d9878cf62a9c6b..fa5383d03b10d5c6560e50ff28be7b6e71ffe2b0 100644 --- a/substrate/primitives/maybe-compressed-blob/Cargo.toml +++ b/substrate/primitives/maybe-compressed-blob/Cargo.toml @@ -14,5 +14,5 @@ readme = "README.md" workspace = true [dependencies] -thiserror = "1.0" +thiserror = { workspace = true } zstd = { version = "0.12.4", default-features = false } diff --git a/substrate/primitives/merkle-mountain-range/Cargo.toml b/substrate/primitives/merkle-mountain-range/Cargo.toml index 59b48b1b83937389550b626d6d6f9cdbecfa76cc..50d8477823948a4005a9cb0ccfabf73349f91222 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -17,15 +17,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.5.2", default-features = false } -serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false, optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { path = "../api", default-features = false } sp-core = { path = "../core", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } -thiserror = "1.0" +thiserror = { optional = true, workspace = true } [dev-dependencies] array-bytes = "6.1" @@ -34,6 +34,7 @@ array-bytes = "6.1" default = ["std"] std = [ "codec/std", + "dep:thiserror", "log/std", "mmr-lib/std", "scale-info/std", diff --git a/substrate/primitives/npos-elections/Cargo.toml b/substrate/primitives/npos-elections/Cargo.toml index 74423ea556c2c841c2415dd7af8776fac5e6891b..7373aa849cb8d8f47fe3cffefd5302eee28a7dfb 100644 --- a/substrate/primitives/npos-elections/Cargo.toml +++ b/substrate/primitives/npos-elections/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-arithmetic = { path = "../arithmetic", default-features = false } sp-core = { path = "../core", default-features = false } sp-runtime = { path = "../runtime", default-features = false } diff --git a/substrate/primitives/npos-elections/fuzzer/Cargo.toml b/substrate/primitives/npos-elections/fuzzer/Cargo.toml index 931773254365f82566b37c766046fcb2aacca653..bcd908b970508f19bd131d9eba08c18306b632c8 100644 --- a/substrate/primitives/npos-elections/fuzzer/Cargo.toml +++ b/substrate/primitives/npos-elections/fuzzer/Cargo.toml @@ -17,7 +17,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["small_rng", "std"] } sp-npos-elections = { path = ".." } diff --git a/substrate/primitives/rpc/Cargo.toml b/substrate/primitives/rpc/Cargo.toml index 735f8ed32da1ffa77a4a2ba693e1c20209478a76..dce0eeee9f99bd9cd544e584e07cd3c29ca20ae1 100644 --- a/substrate/primitives/rpc/Cargo.toml +++ b/substrate/primitives/rpc/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] rustc-hash = "1.1.0" -serde = { version = "1.0.195", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } sp-core = { path = "../core" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } diff --git a/substrate/primitives/runtime-interface/Cargo.toml b/substrate/primitives/runtime-interface/Cargo.toml index 6e046567d1451eec1b40cfbd5aacb24d8761813c..b4fab17eeb7c1872404cbc9d03bc775be6736fe9 100644 --- a/substrate/primitives/runtime-interface/Cargo.toml +++ b/substrate/primitives/runtime-interface/Cargo.toml @@ -29,6 +29,9 @@ primitive-types = { version = "0.12.0", default-features = false } sp-storage = { path = "../storage", default-features = false } impl-trait-for-tuples = "0.2.2" +[target.'cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))'.dependencies] +polkavm-derive = { workspace = true } + [dev-dependencies] sp-runtime-interface-test-wasm = { path = "test-wasm" } sp-state-machine = { path = "../state-machine" } diff --git a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml index c453f9079bbde85176463d130bbed9e22ce736dd..7dbd810fea932fd4bf284f80c1de976be7009b6d 100644 --- a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml @@ -22,6 +22,6 @@ proc-macro = true Inflector = "0.11.4" proc-macro-crate = "3.0.0" proc-macro2 = "1.0.56" -quote = "1.0.28" +quote = { workspace = true } expander = "2.0.0" -syn = { version = "2.0.48", features = ["extra-traits", "fold", "full", "visit"] } +syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs index 77a29bec3807fa41d224bfb22dd3824e4ff1bb6e..32455b39eed6ff2a1757c325ee6907d14cfd8eb1 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs @@ -109,7 +109,12 @@ fn function_no_std_impl( }; let maybe_unreachable = if method.should_trap_on_return() { quote! { - ; core::arch::wasm32::unreachable(); + ; + #[cfg(target_family = "wasm")] + { core::arch::wasm32::unreachable(); } + + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + unsafe { core::arch::asm!("unimp", options(noreturn)); } } } else { quote! {} @@ -118,7 +123,7 @@ fn function_no_std_impl( let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version")); let cfg_wasm_only = if is_wasm_only { - quote! { #[cfg(target_arch = "wasm32")] } + quote! { #[cfg(substrate_runtime)] } } else { quote! {} }; diff --git a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs index 77a9e56eecba5c91e3a36534f8f79da6ead30107..fc985157cdb7f3e5154d8647f38c01a1378e2fbe 100644 --- a/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs +++ b/substrate/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs @@ -116,8 +116,8 @@ fn generate_extern_host_function( #(#cfg_attrs)* #[doc = #doc_string] pub fn #function ( #( #args ),* ) #return_value { + #[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #crate_::polkavm::polkavm_import(abi = #crate_::polkavm::polkavm_abi))] extern "C" { - /// The extern function. pub fn #ext_function ( #( #arg_names: <#arg_types as #crate_::RIType>::FFIType ),* ) #ffi_return_value; diff --git a/substrate/primitives/runtime-interface/src/lib.rs b/substrate/primitives/runtime-interface/src/lib.rs index 1f1638880bb6c8655573ad260aac9cd40e6a5592..8b0edf1ec818e57237e6170c8d27f2d1f492cc46 100644 --- a/substrate/primitives/runtime-interface/src/lib.rs +++ b/substrate/primitives/runtime-interface/src/lib.rs @@ -376,6 +376,9 @@ pub use sp_externalities::{ #[doc(hidden)] pub use codec; +#[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))] +pub mod polkavm; + #[cfg(feature = "std")] pub mod host; pub(crate) mod impls; diff --git a/substrate/primitives/runtime-interface/src/polkavm.rs b/substrate/primitives/runtime-interface/src/polkavm.rs new file mode 100644 index 0000000000000000000000000000000000000000..484a269fd14b7e911091ce9d3c2028df09727c2b --- /dev/null +++ b/substrate/primitives/runtime-interface/src/polkavm.rs @@ -0,0 +1,30 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use polkavm_derive::{polkavm_export, polkavm_import}; + +#[polkavm_derive::polkavm_define_abi(allow_extra_input_registers)] +pub mod polkavm_abi {} + +impl self::polkavm_abi::FromHost for *mut u8 { + type Regs = (u32,); + + #[inline] + fn from_host((value,): Self::Regs) -> Self { + value as *mut u8 + } +} diff --git a/substrate/primitives/runtime-interface/src/util.rs b/substrate/primitives/runtime-interface/src/util.rs index 8db32271a0e7e7b682fb4b7012ea897ca8d02640..86c8e4b50e29310358dd90dca772a9714d986a38 100644 --- a/substrate/primitives/runtime-interface/src/util.rs +++ b/substrate/primitives/runtime-interface/src/util.rs @@ -21,7 +21,7 @@ pub fn pack_ptr_and_len(ptr: u32, len: u32) -> u64 { // The static assertions from above are changed into a runtime check. #[cfg(all(not(feature = "std"), feature = "disable_target_static_assertions"))] - assert_eq!(4, sp_std::mem::size_of::()); + assert_eq!(4, core::mem::size_of::()); (u64::from(len) << 32) | u64::from(ptr) } @@ -34,7 +34,7 @@ pub fn pack_ptr_and_len(ptr: u32, len: u32) -> u64 { pub fn unpack_ptr_and_len(val: u64) -> (u32, u32) { // The static assertions from above are changed into a runtime check. #[cfg(all(not(feature = "std"), feature = "disable_target_static_assertions"))] - assert_eq!(4, sp_std::mem::size_of::()); + assert_eq!(4, core::mem::size_of::()); let ptr = (val & (!0u32 as u64)) as u32; let len = (val >> 32) as u32; diff --git a/substrate/primitives/runtime-interface/src/wasm.rs b/substrate/primitives/runtime-interface/src/wasm.rs index 91205addf21a0fead6c36391c24ff77ee97c951e..10bb50c64039895dd4e5fc6f738f0d249652bbe9 100644 --- a/substrate/primitives/runtime-interface/src/wasm.rs +++ b/substrate/primitives/runtime-interface/src/wasm.rs @@ -19,7 +19,7 @@ use crate::RIType; -use sp_std::cell::Cell; +use core::cell::Cell; /// Something that can be created from a ffi value. /// diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index f4b1158242941286e539c9a87bbc16d57ab1b3af..cacfc059722942f2fe70d951f8016449914c2e5b 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -21,11 +21,11 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = either = { version = "1.5", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.17", default-features = false } +log = { workspace = true } paste = "1.0" rand = { version = "0.8.5", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-application-crypto = { path = "../application-crypto", default-features = false } sp-arithmetic = { path = "../arithmetic", default-features = false } sp-core = { path = "../core", default-features = false } @@ -34,11 +34,11 @@ sp-std = { path = "../std", default-features = false } sp-weights = { path = "../weights", default-features = false } docify = { version = "0.2.7" } -simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", rev = "e48b187bcfd5cc75111acd9d241f1bd36604344b", optional = true } +simple-mermaid = { version = "0.1.1", optional = true } [dev-dependencies] rand = "0.8.5" -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } zstd = { version = "0.12.4", default-features = false } sp-api = { path = "../api" } sp-state-machine = { path = "../state-machine" } diff --git a/substrate/primitives/runtime/src/generic/header.rs b/substrate/primitives/runtime/src/generic/header.rs index 0eeef363a06dc95e999a0277563e1c86f46e7028..d78aa5c8d3c29ea35eacecdbd0c40ebe0bfec926 100644 --- a/substrate/primitives/runtime/src/generic/header.rs +++ b/substrate/primitives/runtime/src/generic/header.rs @@ -133,7 +133,7 @@ where impl Header where Number: Member - + sp_std::hash::Hash + + core::hash::Hash + Copy + MaybeDisplay + AtLeast32BitUnsigned diff --git a/substrate/primitives/runtime/src/offchain/storage_lock.rs b/substrate/primitives/runtime/src/offchain/storage_lock.rs index a2da48721e7591909932e58cb20fffbeedb5f88d..56d0eeae527cf072c4b7756690956ab192c5e805 100644 --- a/substrate/primitives/runtime/src/offchain/storage_lock.rs +++ b/substrate/primitives/runtime/src/offchain/storage_lock.rs @@ -66,9 +66,9 @@ use crate::{ traits::BlockNumberProvider, }; use codec::{Codec, Decode, Encode}; +use core::fmt; use sp_core::offchain::{Duration, Timestamp}; use sp_io::offchain; -use sp_std::fmt; /// Default expiry duration for time based locks in milliseconds. const STORAGE_LOCK_DEFAULT_EXPIRY_DURATION: Duration = Duration::from_millis(20_000); diff --git a/substrate/primitives/runtime/src/traits.rs b/substrate/primitives/runtime/src/traits.rs index 4213117334ee7860f0600af2e8033c24b550938b..caede5e2b59a7e3843c1ec966a02f2bbd86f2a84 100644 --- a/substrate/primitives/runtime/src/traits.rs +++ b/substrate/primitives/runtime/src/traits.rs @@ -540,6 +540,9 @@ morph_types! { /// Morpher to disregard the source value and replace with another. pub type Replace = |_| -> V::Type { V::get() }; + /// Morpher to disregard the source value and replace with the default of `V`. + pub type ReplaceWithDefault = |_| -> V { Default::default() }; + /// Mutator which reduces a scalar by a particular amount. pub type ReduceBy = |r: N::Type| -> N::Type { r.checked_sub(&N::get()).unwrap_or(Zero::zero()) diff --git a/substrate/primitives/staking/Cargo.toml b/substrate/primitives/staking/Cargo.toml index e1ab87bef0999d89a6b496f8acef640e15de36f1..21346fbaca5383554d1285ceeaba43815b81857a 100644 --- a/substrate/primitives/staking/Cargo.toml +++ b/substrate/primitives/staking/Cargo.toml @@ -16,7 +16,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index c2ac5ae004b1b7621341020e614a9c769e27d2dd..f5b4a1ed63fb3da2e9d69f51f8bc10355dbd0730 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -150,6 +150,9 @@ pub trait OnStakingUpdate { _slashed_total: Balance, ) { } + + /// Fired when a portion of a staker's balance has been withdrawn. + fn on_withdraw(_stash: &AccountId, _amount: Balance) {} } /// A generic representation of a staking implementation. diff --git a/substrate/primitives/state-machine/Cargo.toml b/substrate/primitives/state-machine/Cargo.toml index b63d5685a331a88d659e13ea9d15b9f17becc402..09994f1ae91e1c9a7dba2faf169870f08a640b22 100644 --- a/substrate/primitives/state-machine/Cargo.toml +++ b/substrate/primitives/state-machine/Cargo.toml @@ -19,11 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } hash-db = { version = "0.16.0", default-features = false } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } parking_lot = { version = "0.12.1", optional = true } rand = { version = "0.8.5", optional = true } smallvec = "1.11.0" -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } tracing = { version = "0.1.29", optional = true } sp-core = { path = "../core", default-features = false } sp-externalities = { path = "../externalities", default-features = false } diff --git a/substrate/primitives/state-machine/src/error.rs b/substrate/primitives/state-machine/src/error.rs index 4e8e02a26025ca9ef0f6899d3870e9e96c96da22..cf8ca4425c119e8960f33def2e6b2eaa35afbd44 100644 --- a/substrate/primitives/state-machine/src/error.rs +++ b/substrate/primitives/state-machine/src/error.rs @@ -16,7 +16,7 @@ // limitations under the License. /// State Machine Errors -use sp_std::fmt; +use core::fmt; /// State Machine Error bound. /// diff --git a/substrate/primitives/state-machine/src/in_memory_backend.rs b/substrate/primitives/state-machine/src/in_memory_backend.rs index ce551cec2a473ceacbc1ab99984af2e5ed4b64f4..06fe6d4162a7f4c920c8f09c2e4e8b05a3c72c03 100644 --- a/substrate/primitives/state-machine/src/in_memory_backend.rs +++ b/substrate/primitives/state-machine/src/in_memory_backend.rs @@ -79,7 +79,7 @@ where /// Apply the given transaction to this backend and set the root to the given value. pub fn apply_transaction(&mut self, root: H::Out, transaction: PrefixedMemoryDB) { - let mut storage = sp_std::mem::take(self).into_storage(); + let mut storage = core::mem::take(self).into_storage(); storage.consolidate(transaction); *self = TrieBackendBuilder::new(storage, root).build(); diff --git a/substrate/primitives/state-machine/src/stats.rs b/substrate/primitives/state-machine/src/stats.rs index 7c5510961a373268ca44693489bed842209c1e03..84ab7725660da607442b55c4015be656dcd4fa55 100644 --- a/substrate/primitives/state-machine/src/stats.rs +++ b/substrate/primitives/state-machine/src/stats.rs @@ -17,7 +17,7 @@ //! Usage statistics for state db -use sp_std::cell::RefCell; +use core::cell::RefCell; #[cfg(feature = "std")] use std::time::{Duration, Instant}; diff --git a/substrate/primitives/statement-store/Cargo.toml b/substrate/primitives/statement-store/Cargo.toml index 14af57a7b419b5a5db29065106a8853a6a9bf493..652ab3ef13aa695f13c9b9c0a525b3f90dff8f0c 100644 --- a/substrate/primitives/statement-store/Cargo.toml +++ b/substrate/primitives/statement-store/Cargo.toml @@ -26,7 +26,7 @@ sp-api = { path = "../api", default-features = false } sp-application-crypto = { path = "../application-crypto", default-features = false } sp-runtime-interface = { path = "../runtime-interface", default-features = false } sp-externalities = { path = "../externalities", default-features = false } -thiserror = { version = "1.0", optional = true } +thiserror = { optional = true, workspace = true } # ECIES dependencies ed25519-dalek = { version = "2.1", optional = true } diff --git a/substrate/primitives/storage/Cargo.toml b/substrate/primitives/storage/Cargo.toml index 4c2f7cadefeb5a4100006f73fa595854fffa539e..d3ade87ea47f848e8036ce0a4d374fa4d77184b2 100644 --- a/substrate/primitives/storage/Cargo.toml +++ b/substrate/primitives/storage/Cargo.toml @@ -20,7 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } impl-serde = { version = "0.4.0", optional = true, default-features = false } ref-cast = "1.0.0" -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-debug-derive = { path = "../debug-derive", default-features = false } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/test-primitives/Cargo.toml b/substrate/primitives/test-primitives/Cargo.toml index 3649217cf74e136f88860d0c69466d3de9ef5a06..f310216dd58d737be7cde512d3fbdf27f4fbda25 100644 --- a/substrate/primitives/test-primitives/Cargo.toml +++ b/substrate/primitives/test-primitives/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["derive"], optional = true } +serde = { features = ["derive"], optional = true, workspace = true } sp-application-crypto = { path = "../application-crypto", default-features = false } sp-core = { path = "../core", default-features = false } sp-runtime = { path = "../runtime", default-features = false } diff --git a/substrate/primitives/timestamp/Cargo.toml b/substrate/primitives/timestamp/Cargo.toml index 914c1e7865e972eb5f8acdba7d4cc9103ceb18f6..9e2b802bfb15e2e89169923d2e6f4274d2cdf1f2 100644 --- a/substrate/primitives/timestamp/Cargo.toml +++ b/substrate/primitives/timestamp/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.74", optional = true } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } sp-inherents = { path = "../inherents", default-features = false } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/timestamp/src/lib.rs b/substrate/primitives/timestamp/src/lib.rs index d1bd2a3446e6b65ea2bf7a35942cae5f10f599be..a91ee16afe8e33a2ad2faa719db8c3a168540589 100644 --- a/substrate/primitives/timestamp/src/lib.rs +++ b/substrate/primitives/timestamp/src/lib.rs @@ -20,8 +20,8 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; +use core::time::Duration; use sp_inherents::{InherentData, InherentIdentifier, IsFatalError}; -use sp_std::time::Duration; /// The identifier for the `timestamp` inherent. pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"timstap0"; @@ -69,7 +69,7 @@ impl Timestamp { } } -impl sp_std::ops::Deref for Timestamp { +impl core::ops::Deref for Timestamp { type Target = u64; fn deref(&self) -> &Self::Target { @@ -219,7 +219,7 @@ impl InherentDataProvider { } #[cfg(feature = "std")] -impl sp_std::ops::Deref for InherentDataProvider { +impl core::ops::Deref for InherentDataProvider { type Target = InherentType; fn deref(&self) -> &Self::Target { diff --git a/substrate/primitives/trie/Cargo.toml b/substrate/primitives/trie/Cargo.toml index f871f462d363375ac85cbceb2c1687b0a5daacc6..16d3ca19a179ce97bbc2c5c42023ceb54bf8b32b 100644 --- a/substrate/primitives/trie/Cargo.toml +++ b/substrate/primitives/trie/Cargo.toml @@ -30,7 +30,7 @@ nohash-hasher = { version = "0.2.0", optional = true } parking_lot = { version = "0.12.1", optional = true } rand = { version = "0.8", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.48", optional = true } +thiserror = { optional = true, workspace = true } tracing = { version = "0.1.29", optional = true } trie-db = { version = "0.28.0", default-features = false } trie-root = { version = "0.18.0", default-features = false } diff --git a/substrate/primitives/trie/src/node_header.rs b/substrate/primitives/trie/src/node_header.rs index c118ee07b8cbd362b457d4b496861beec435d34d..dc33e6bd614928bef05c9c840b9708ca80f065f2 100644 --- a/substrate/primitives/trie/src/node_header.rs +++ b/substrate/primitives/trie/src/node_header.rs @@ -19,7 +19,7 @@ use crate::trie_constants; use codec::{Decode, Encode, Input, Output}; -use sp_std::iter::once; +use core::iter::once; /// A node header #[derive(Copy, Clone, PartialEq, Eq, sp_core::RuntimeDebug)] @@ -118,7 +118,7 @@ pub(crate) fn size_and_prefix_iterator( prefix_mask: usize, ) -> impl Iterator { let max_value = 255u8 >> prefix_mask; - let l1 = sp_std::cmp::min((max_value as usize).saturating_sub(1), size); + let l1 = core::cmp::min((max_value as usize).saturating_sub(1), size); let (first_byte, mut rem) = if size == l1 { (once(prefix + l1 as u8), 0) } else { @@ -138,7 +138,7 @@ pub(crate) fn size_and_prefix_iterator( None } }; - first_byte.chain(sp_std::iter::from_fn(next_bytes)) + first_byte.chain(core::iter::from_fn(next_bytes)) } /// Encodes size and prefix to a stream output. diff --git a/substrate/primitives/version/Cargo.toml b/substrate/primitives/version/Cargo.toml index 7de762d71e9948d8a36b93a21ef2acf7c3b459f8..a94e2322430b421d24b438b2899b67feec9dd059 100644 --- a/substrate/primitives/version/Cargo.toml +++ b/substrate/primitives/version/Cargo.toml @@ -21,8 +21,8 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = impl-serde = { version = "0.4.0", default-features = false, optional = true } parity-wasm = { version = "0.45", optional = true } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, features = ["alloc", "derive"], optional = true } -thiserror = { version = "1.0.48", optional = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } +thiserror = { optional = true, workspace = true } sp-crypto-hashing-proc-macro = { path = "../crypto/hashing/proc-macro" } sp-runtime = { path = "../runtime", default-features = false } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/version/proc-macro/Cargo.toml b/substrate/primitives/version/proc-macro/Cargo.toml index 9e90940825d5f12b76ca2e46e0593da65f6d1036..f7abf88c9a67b7e3102d44254b520a9bbeb7a8a8 100644 --- a/substrate/primitives/version/proc-macro/Cargo.toml +++ b/substrate/primitives/version/proc-macro/Cargo.toml @@ -21,8 +21,8 @@ proc-macro = true [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } proc-macro2 = "1.0.56" -quote = "1.0.28" -syn = { version = "2.0.48", features = ["extra-traits", "fold", "full", "visit"] } +quote = { workspace = true } +syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } [dev-dependencies] sp-version = { path = ".." } diff --git a/substrate/primitives/wasm-interface/Cargo.toml b/substrate/primitives/wasm-interface/Cargo.toml index ccd2a3043c5c9f319681ae9306c82ad0336f9a2b..f7d1038903eab6954374e6dac141b74683315a25 100644 --- a/substrate/primitives/wasm-interface/Cargo.toml +++ b/substrate/primitives/wasm-interface/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.17", optional = true } +log = { optional = true, workspace = true, default-features = true } wasmtime = { version = "8.0.1", default-features = false, optional = true } anyhow = { version = "1.0.68", optional = true } sp-std = { path = "../std", default-features = false } diff --git a/substrate/primitives/weights/Cargo.toml b/substrate/primitives/weights/Cargo.toml index 3a57720b964e4bffd9929547d3f519453ea0b8b7..a7d61de001b5525af1a43148a6c7f6f284c4e51f 100644 --- a/substrate/primitives/weights/Cargo.toml +++ b/substrate/primitives/weights/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] bounded-collections = { version = "0.2.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.195", default-features = false, optional = true, features = ["alloc", "derive"] } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } smallvec = "1.11.0" sp-arithmetic = { path = "../arithmetic", default-features = false } sp-debug-derive = { path = "../debug-derive", default-features = false } diff --git a/substrate/primitives/weights/src/lib.rs b/substrate/primitives/weights/src/lib.rs index ef431bddee265fab663efd1e5b039a1c3e78bdc4..aede9473535a361325f64c177dd9f1861664ca40 100644 --- a/substrate/primitives/weights/src/lib.rs +++ b/substrate/primitives/weights/src/lib.rs @@ -235,7 +235,7 @@ where } /// Implementor of `WeightToFee` that maps one unit of weight to one unit of fee. -pub struct IdentityFee(sp_std::marker::PhantomData); +pub struct IdentityFee(core::marker::PhantomData); impl WeightToFee for IdentityFee where @@ -249,7 +249,7 @@ where } /// Implementor of [`WeightToFee`] such that it maps any unit of weight to a fixed fee. -pub struct FixedFee(sp_std::marker::PhantomData); +pub struct FixedFee(core::marker::PhantomData); impl WeightToFee for FixedFee where @@ -275,7 +275,7 @@ pub type NoFee = FixedFee<0, T>; /// // Results in a multiplier of 10 for each unit of weight (or length) /// type LengthToFee = ConstantMultiplier::>; /// ``` -pub struct ConstantMultiplier(sp_std::marker::PhantomData<(T, M)>); +pub struct ConstantMultiplier(core::marker::PhantomData<(T, M)>); impl WeightToFee for ConstantMultiplier where diff --git a/substrate/primitives/weights/src/weight_meter.rs b/substrate/primitives/weights/src/weight_meter.rs index 584d22304c3ae33ece669bc69927a2568fb9b288..1738948e4c3c36ad5744ee3469589d7167306178 100644 --- a/substrate/primitives/weights/src/weight_meter.rs +++ b/substrate/primitives/weights/src/weight_meter.rs @@ -149,6 +149,11 @@ impl WeightMeter { pub fn can_consume(&self, w: Weight) -> bool { self.consumed.checked_add(&w).map_or(false, |t| t.all_lte(self.limit)) } + + /// Reclaim the given weight. + pub fn reclaim_proof_size(&mut self, s: u64) { + self.consumed.saturating_reduce(Weight::from_parts(0, s)); + } } #[cfg(test)] @@ -277,6 +282,21 @@ mod tests { assert_eq!(meter.consumed(), Weight::from_parts(5, 10)); } + #[test] + #[cfg(debug_assertions)] + fn reclaim_works() { + let mut meter = WeightMeter::with_limit(Weight::from_parts(5, 10)); + + meter.consume(Weight::from_parts(5, 10)); + assert_eq!(meter.consumed(), Weight::from_parts(5, 10)); + + meter.reclaim_proof_size(3); + assert_eq!(meter.consumed(), Weight::from_parts(5, 7)); + + meter.reclaim_proof_size(10); + assert_eq!(meter.consumed(), Weight::from_parts(5, 0)); + } + #[test] #[cfg(debug_assertions)] #[should_panic(expected = "Weight counter overflow")] diff --git a/substrate/scripts/ci/node-template-release/Cargo.toml b/substrate/scripts/ci/node-template-release/Cargo.toml index a0b93184546288b1c326e1f2584545dc12a0c7d6..cc60d0a4510132c543162c3bcf9964e35644b24e 100644 --- a/substrate/scripts/ci/node-template-release/Cargo.toml +++ b/substrate/scripts/ci/node-template-release/Cargo.toml @@ -14,7 +14,7 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } flate2 = "1.0" fs_extra = "1.3" glob = "0.3" diff --git a/substrate/scripts/ci/node-template-release/src/main.rs b/substrate/scripts/ci/node-template-release/src/main.rs index fc8089f3051fe14a7608bcfad1418069d3bfcff9..2bcda2944fc90e62ed947af54c7be680c1038f38 100644 --- a/substrate/scripts/ci/node-template-release/src/main.rs +++ b/substrate/scripts/ci/node-template-release/src/main.rs @@ -18,6 +18,7 @@ use std::{ collections::HashMap, + ffi::OsString, fs::{self, File, OpenOptions}, path::{Path, PathBuf}, process::Command, @@ -56,9 +57,24 @@ struct Options { } /// Copy the `node-template` to the given path. -fn copy_node_template(node_template: &Path, dest_path: &Path) { +fn copy_node_template(node_template: &Path, node_template_folder: &OsString, dest_path: &Path) { let options = CopyOptions::new(); dir::copy(node_template, dest_path, &options).expect("Copies node-template to tmp dir"); + + let dest_path = dest_path.join(node_template_folder); + + dir::get_dir_content(dest_path.join("env-setup")) + .expect("`env-setup` directory should exist") + .files + .iter() + .for_each(|f| { + fs::copy( + f, + dest_path.join(PathBuf::from(f).file_name().expect("File has a file name.")), + ) + .expect("Copying from `env-setup` directory works"); + }); + dir::remove(dest_path.join("env-setup")).expect("Deleting `env-setup works`"); } /// Find all `Cargo.toml` files in the given path. @@ -195,6 +211,9 @@ fn update_root_cargo_toml( commit_id: &str, ) { let mut workspace = Table::new(); + + workspace.insert("resolver", value("2")); + workspace.insert("members", value(Array::from_iter(members.iter()))); let mut workspace_dependencies = Table::new(); deps.values() @@ -216,6 +235,8 @@ fn update_root_cargo_toml( workspace.insert("package", Item::Table(package)); workspace.insert("dependencies", Item::Table(workspace_dependencies)); + + workspace.insert("lints", Item::Table(Table::new())); cargo_toml.insert("workspace", Item::Table(workspace)); let mut panic_unwind = Table::new(); @@ -294,7 +315,7 @@ fn main() { .file_name() .expect("Node template folder is last element of path") .to_owned(); - copy_node_template(&options.node_template, build_dir.path()); + copy_node_template(&options.node_template, &node_template_folder, build_dir.path()); // The path to the node-template in the build dir. let node_template_path = build_dir.path().join(node_template_folder); @@ -429,6 +450,7 @@ frame-system = { workspace = true } ); let expected_toml = r#"[workspace] +resolver = "2" members = ["node", "pallets/template", "runtime"] [workspace.package] @@ -438,6 +460,8 @@ edition = "2021" frame-system = { version = "4.0.0-dev", default-features = true, git = "https://github.com/paritytech/polkadot-sdk.git", rev = "commit_id" } sp-io = { version = "7.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", rev = "commit_id" } +[workspace.lints] + [profile] [profile.release] diff --git a/substrate/scripts/run_all_benchmarks.sh b/substrate/scripts/run_all_benchmarks.sh index 83848100a7e5189f312411fb42eae7d4eb6a43c4..6dd7cede319f974a074d951d34ec3c970f02c32f 100755 --- a/substrate/scripts/run_all_benchmarks.sh +++ b/substrate/scripts/run_all_benchmarks.sh @@ -59,11 +59,12 @@ done if [ "$skip_build" != true ] then echo "[+] Compiling Substrate benchmarks..." - cargo build --profile=production --locked --features=runtime-benchmarks --bin substrate + cargo build --profile=production --locked --features=runtime-benchmarks --bin substrate-node fi # The executable to use. -SUBSTRATE=./target/production/substrate +# Parent directory because of the monorepo structure. +SUBSTRATE=../target/production/substrate-node # Manually exclude some pallets. EXCLUDED_PALLETS=( @@ -80,11 +81,7 @@ EXCLUDED_PALLETS=( # Load all pallet names in an array. ALL_PALLETS=($( - $SUBSTRATE benchmark pallet --list --chain=dev |\ - tail -n+2 |\ - cut -d',' -f1 |\ - sort |\ - uniq + $SUBSTRATE benchmark pallet --list=pallets --no-csv-header --chain=dev )) # Filter out the excluded pallets by concatenating the arrays and discarding duplicates. diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml index 14f55f6454659efb6abf4d0eabf4f37dc53212c9..349b04d32d7baff5c24323e171ebe373730f464f 100644 --- a/substrate/test-utils/client/Cargo.toml +++ b/substrate/test-utils/client/Cargo.toml @@ -20,8 +20,8 @@ array-bytes = "6.1" async-trait = "0.1.74" codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -serde = "1.0.195" -serde_json = "1.0.111" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sc-client-api = { path = "../../client/api" } sc-client-db = { path = "../../client/db", default-features = false, features = [ "test-helpers", diff --git a/substrate/test-utils/client/src/lib.rs b/substrate/test-utils/client/src/lib.rs index e3f06e27563581b100f489072d939b02d99a3247..d283b24f286aed743ba613f095f2cd319caba263 100644 --- a/substrate/test-utils/client/src/lib.rs +++ b/substrate/test-utils/client/src/lib.rs @@ -72,6 +72,7 @@ pub struct TestClientBuilder, bad_blocks: BadBlocks, enable_offchain_indexing_api: bool, + enable_import_proof_recording: bool, no_genesis: bool, } @@ -120,6 +121,7 @@ impl bad_blocks: None, enable_offchain_indexing_api: false, no_genesis: false, + enable_import_proof_recording: false, } } @@ -165,6 +167,12 @@ impl self } + /// Enable proof recording on import. + pub fn enable_import_proof_recording(mut self) -> Self { + self.enable_import_proof_recording = true; + self + } + /// Disable writing genesis. pub fn set_no_genesis(mut self) -> Self { self.no_genesis = true; @@ -202,6 +210,7 @@ impl }; let client_config = ClientConfig { + enable_import_proof_recording: self.enable_import_proof_recording, offchain_indexing_api: self.enable_offchain_indexing_api, no_genesis: self.no_genesis, ..Default::default() diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index 589686c1229432acbbeac9885213a019531174a9..3bba5cd5bf0473f9f26bfc56619fc9ee8d098414 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -51,7 +51,7 @@ sp-externalities = { path = "../../primitives/externalities", default-features = # 3rd party array-bytes = { version = "6.1", optional = true } -log = { version = "0.4.17", default-features = false } +log = { workspace = true } [dev-dependencies] futures = "0.3.21" @@ -62,8 +62,8 @@ sc-executor-common = { path = "../../client/executor/common" } sp-consensus = { path = "../../primitives/consensus/common" } substrate-test-runtime-client = { path = "client" } sp-tracing = { path = "../../primitives/tracing" } -serde = { version = "1.0.195", features = ["alloc", "derive"], default-features = false } -serde_json = { version = "1.0.111", default-features = false, features = ["alloc"] } +serde = { features = ["alloc", "derive"], workspace = true } +serde_json = { features = ["alloc"], workspace = true } [build-dependencies] substrate-wasm-builder = { path = "../../utils/wasm-builder", optional = true } diff --git a/substrate/test-utils/runtime/src/genesismap.rs b/substrate/test-utils/runtime/src/genesismap.rs index 5ed9c8a645886f741611bc9fdc5d4eae01ae2704..9e972886b3771469284a7ee66e5f823623a75b07 100644 --- a/substrate/test-utils/runtime/src/genesismap.rs +++ b/substrate/test-utils/runtime/src/genesismap.rs @@ -124,7 +124,6 @@ impl GenesisStorageBuilder { .into_iter() .map(|x| (x.into(), 1)) .collect(), - epoch_config: Some(crate::TEST_RUNTIME_BABE_EPOCH_CONFIGURATION), ..Default::default() }, substrate_test: substrate_test_pallet::GenesisConfig { diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 8bc6f72a82ec2d0690c24565e841931bada92f05..db9ff187b707fa66c70be24844297b7f74844e4a 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -1291,7 +1291,7 @@ mod tests { let r = Vec::::decode(&mut &r[..]).unwrap(); let json = String::from_utf8(r.into()).expect("returned value is json. qed."); - let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":null},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#; + let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":{"c":[1,4],"allowed_slots":"PrimaryAndSecondaryVRFSlots"}},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#; assert_eq!(expected.to_string(), json); } diff --git a/substrate/test-utils/runtime/transaction-pool/Cargo.toml b/substrate/test-utils/runtime/transaction-pool/Cargo.toml index b52a897438b6854a066a95e51ba49bd0eddd7f84..33e56e8e55e7a7479fb1c783af818f3120e1b7f4 100644 --- a/substrate/test-utils/runtime/transaction-pool/Cargo.toml +++ b/substrate/test-utils/runtime/transaction-pool/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" parking_lot = "0.12.1" -thiserror = "1.0" +thiserror = { workspace = true } sc-transaction-pool = { path = "../../../client/transaction-pool" } sc-transaction-pool-api = { path = "../../../client/transaction-pool/api" } sp-blockchain = { path = "../../../primitives/blockchain" } diff --git a/substrate/utils/binary-merkle-tree/Cargo.toml b/substrate/utils/binary-merkle-tree/Cargo.toml index b0b870823fbf42b1dc3790978f460c66dac1e8f5..6ba515afee175751840edadfb7e6fc2ab01c5e46 100644 --- a/substrate/utils/binary-merkle-tree/Cargo.toml +++ b/substrate/utils/binary-merkle-tree/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] array-bytes = { version = "6.1", optional = true } -log = { version = "0.4", default-features = false, optional = true } +log = { optional = true, workspace = true } hash-db = { version = "0.16.0", default-features = false } [dev-dependencies] diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 0314e3035c027ea34aa26a1b259ceb5ea8451d89..a2db83052c54508b14e37805cbb4b004542c6f30 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = "6.1" chrono = "0.4" -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.6.1" } comfy-table = { version = "7.1.0", default-features = false } handlebars = "4.2.2" @@ -26,12 +26,12 @@ Inflector = "0.11.4" itertools = "0.10.3" lazy_static = "1.4.0" linked-hash-map = "0.5.4" -log = "0.4.17" +log = { workspace = true, default-features = true } rand = { version = "0.8.5", features = ["small_rng"] } rand_pcg = "0.3.1" -serde = "1.0.195" -serde_json = "1.0.111" -thiserror = "1.0.48" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } thousands = "0.2.0" frame-benchmarking = { path = "../../../frame/benchmarking" } frame-support = { path = "../../../frame/support" } diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs index 5c76ca68e85f5708cbccde38c5f35cb4046033c5..c6ba282401672335a6a72e698533a4af35670523 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{writer, PalletCmd}; +use super::{writer, ListOutput, PalletCmd}; use codec::{Decode, Encode}; use frame_benchmarking::{ Analysis, BenchmarkBatch, BenchmarkBatchSplitResults, BenchmarkList, BenchmarkParameter, @@ -37,9 +37,15 @@ use sp_core::{ }; use sp_externalities::Extensions; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use sp_runtime::traits::Hash; use sp_state_machine::StateMachine; -use std::{collections::HashMap, fmt::Debug, fs, str::FromStr, time}; +use std::{ + collections::{BTreeMap, BTreeSet, HashMap}, + fmt::Debug, + fs, + str::FromStr, + time, +}; /// Logging target const LOG_TARGET: &'static str = "frame::benchmark::pallet"; @@ -140,11 +146,10 @@ This could mean that you either did not build the node correctly with the \ not created by a node that was compiled with the flag"; impl PalletCmd { - /// Runs the command and benchmarks the chain. - pub fn run(&self, config: Configuration) -> Result<()> + /// Runs the command and benchmarks a pallet. + pub fn run(&self, config: Configuration) -> Result<()> where - BB: BlockT + Debug, - <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, + Hasher: Hash, ExtraHostFunctions: sp_wasm_interface::HostFunctions, { let _d = self.execution.as_ref().map(|exec| { @@ -192,6 +197,7 @@ impl PalletCmd { let spec = config.chain_spec; let pallet = self.pallet.clone().unwrap_or_default(); let pallet = pallet.as_bytes(); + let extrinsic = self.extrinsic.clone().unwrap_or_default(); let extrinsic_split: Vec<&str> = extrinsic.split(',').collect(); let extrinsics: Vec<_> = extrinsic_split.iter().map(|x| x.trim().as_bytes()).collect(); @@ -199,7 +205,7 @@ impl PalletCmd { let genesis_storage = spec.build_storage()?; let mut changes = Default::default(); let cache_size = Some(self.database_cache_size as usize); - let state_with_tracking = BenchmarkingState::::new( + let state_with_tracking = BenchmarkingState::::new( genesis_storage.clone(), cache_size, // Record proof size @@ -207,7 +213,7 @@ impl PalletCmd { // Enable storage tracking true, )?; - let state_without_tracking = BenchmarkingState::::new( + let state_without_tracking = BenchmarkingState::::new( genesis_storage, cache_size, // Do not record proof size @@ -292,16 +298,23 @@ impl PalletCmd { // Convert `Vec` to `String` for better readability. let benchmarks_to_run: Vec<_> = benchmarks_to_run .into_iter() - .map(|b| { + .map(|(pallet, extrinsic, components, pov_modes)| { + let pallet_name = + String::from_utf8(pallet.clone()).expect("Encoded from String; qed"); + let extrinsic_name = + String::from_utf8(extrinsic.clone()).expect("Encoded from String; qed"); ( - b.0, - b.1, - b.2, - b.3.into_iter() + pallet, + extrinsic, + components, + pov_modes + .into_iter() .map(|(p, s)| { (String::from_utf8(p).unwrap(), String::from_utf8(s).unwrap()) }) .collect(), + pallet_name, + extrinsic_name, ) }) .collect(); @@ -310,9 +323,8 @@ impl PalletCmd { return Err("No benchmarks found which match your input.".into()) } - if self.list { - // List benchmarks instead of running them - list_benchmark(benchmarks_to_run); + if let Some(list_output) = self.list { + list_benchmark(benchmarks_to_run, list_output, self.no_csv_header); return Ok(()) } @@ -324,12 +336,12 @@ impl PalletCmd { let mut component_ranges = HashMap::<(Vec, Vec), Vec>::new(); let pov_modes = Self::parse_pov_modes(&benchmarks_to_run)?; - for (pallet, extrinsic, components, _) in benchmarks_to_run.clone() { + for (pallet, extrinsic, components, _, pallet_name, extrinsic_name) in + benchmarks_to_run.clone() + { 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"), + "Starting benchmark: {pallet_name}::{extrinsic_name}" ); let all_components = if components.is_empty() { vec![Default::default()] @@ -410,12 +422,7 @@ impl PalletCmd { ) .map_err(|e| format!("Failed to decode benchmark results: {:?}", e))? .map_err(|e| { - format!( - "Benchmark {}::{} failed: {}", - String::from_utf8_lossy(&pallet), - String::from_utf8_lossy(&extrinsic), - e - ) + format!("Benchmark {pallet_name}::{extrinsic_name} failed: {e}",) })?; } // Do one loop of DB tracking. @@ -489,11 +496,7 @@ impl PalletCmd { log::info!( target: LOG_TARGET, - "Running benchmark: {}.{}({} args) {}/{} {}/{}", - String::from_utf8(pallet.clone()) - .expect("Encoded from String; qed"), - String::from_utf8(extrinsic.clone()) - .expect("Encoded from String; qed"), + "Running benchmark: {pallet_name}::{extrinsic_name}({} args) {}/{} {}/{}", components.len(), s + 1, // s starts at 0. all_components.len(), @@ -702,12 +705,14 @@ impl PalletCmd { Vec, Vec<(BenchmarkParameter, u32, u32)>, Vec<(String, String)>, + String, + String, )>, ) -> Result { use std::collections::hash_map::Entry; let mut parsed = PovModesMap::new(); - for (pallet, call, _components, pov_modes) in benchmarks { + for (pallet, call, _components, pov_modes, _, _) in benchmarks { for (pallet_storage, mode) in pov_modes { let mode = PovEstimationMode::from_str(&mode)?; let splits = pallet_storage.split("::").collect::>(); @@ -756,19 +761,45 @@ impl CliConfiguration for PalletCmd { /// List the benchmarks available in the runtime, in a CSV friendly format. fn list_benchmark( - mut benchmarks_to_run: Vec<( + benchmarks_to_run: Vec<( Vec, Vec, Vec<(BenchmarkParameter, u32, u32)>, Vec<(String, String)>, + String, + String, )>, + list_output: ListOutput, + no_csv_header: bool, ) { - // Sort and de-dub by pallet and function name. - benchmarks_to_run.sort_by(|(pa, sa, _, _), (pb, sb, _, _)| (pa, sa).cmp(&(pb, sb))); - benchmarks_to_run.dedup_by(|(pa, sa, _, _), (pb, sb, _, _)| (pa, sa) == (pb, sb)); + let mut benchmarks = BTreeMap::new(); - println!("pallet, benchmark"); - for (pallet, extrinsic, _, _) in benchmarks_to_run { - println!("{}, {}", String::from_utf8_lossy(&pallet), String::from_utf8_lossy(&extrinsic)); + // Sort and de-dub by pallet and function name. + benchmarks_to_run.iter().for_each(|(_, _, _, _, pallet_name, extrinsic_name)| { + benchmarks + .entry(pallet_name) + .or_insert_with(BTreeSet::new) + .insert(extrinsic_name); + }); + + match list_output { + ListOutput::All => { + if !no_csv_header { + println!("pallet,extrinsic"); + } + for (pallet, extrinsics) in benchmarks { + for extrinsic in extrinsics { + println!("{pallet},{extrinsic}"); + } + } + }, + ListOutput::Pallets => { + if !no_csv_header { + println!("pallet"); + }; + for pallet in benchmarks.keys() { + println!("{pallet}"); + } + }, } } diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs index c69ce1765fc9dc490f1e0f40966ca07948b6b523..6dc56c0724eaffde1d70d25ef1161b29e01df0c4 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -19,6 +19,7 @@ mod command; mod writer; use crate::shared::HostInfoParams; +use clap::ValueEnum; use sc_cli::{ WasmExecutionMethod, WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, @@ -31,17 +32,32 @@ fn parse_pallet_name(pallet: &str) -> std::result::Result { Ok(pallet.replace("-", "_")) } +/// List options for available benchmarks. +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum ListOutput { + /// List all available pallets and extrinsics. + All, + /// List all available pallets only. + Pallets, +} + /// Benchmark the extrinsic weight of FRAME Pallets. #[derive(Debug, clap::Parser)] pub struct PalletCmd { /// Select a FRAME Pallet to benchmark, or `*` for all (in which case `extrinsic` must be `*`). - #[arg(short, long, value_parser = parse_pallet_name, required_unless_present_any = ["list", "json_input"])] + #[arg(short, long, value_parser = parse_pallet_name, required_unless_present_any = ["list", "json_input", "all"], default_value_if("all", "true", Some("*".into())))] pub pallet: Option, /// Select an extrinsic inside the pallet to benchmark, or `*` for all. - #[arg(short, long, required_unless_present_any = ["list", "json_input"])] + #[arg(short, long, required_unless_present_any = ["list", "json_input", "all"], default_value_if("all", "true", Some("*".into())))] pub extrinsic: Option, + /// Run benchmarks for all pallets and extrinsics. + /// + /// This is equivalent to running `--pallet * --extrinsic *`. + #[arg(long)] + pub all: bool, + /// Select how many samples we should take across the variable components. #[arg(short, long, default_value_t = 50)] pub steps: u32, @@ -158,11 +174,15 @@ pub struct PalletCmd { #[arg(long = "db-cache", value_name = "MiB", default_value_t = 1024)] pub database_cache_size: u32, - /// List the benchmarks that match your query rather than running them. + /// List and print available benchmarks in a csv-friendly format. /// - /// When nothing is provided, we list all benchmarks. - #[arg(long)] - pub list: bool, + /// NOTE: `num_args` and `require_equals` are required to allow `--list` + #[arg(long, value_enum, ignore_case = true, num_args = 0..=1, require_equals = true, default_missing_value("All"))] + pub list: Option, + + /// Don't include csv header when listing benchmarks. + #[arg(long, requires("list"))] + pub no_csv_header: bool, /// If enabled, the storage info is not displayed in the output next to the analysis. /// diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs index 9493a693bbed321785bf0d23326d0f68b3efd534..bd4b65d8a2e378d543b54bebd8db4a1c3d378f98 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -153,8 +153,8 @@ fn map_results( continue } - let pallet_string = String::from_utf8(batch.pallet.clone()).unwrap(); - let instance_string = String::from_utf8(batch.instance.clone()).unwrap(); + let pallet_name = String::from_utf8(batch.pallet.clone()).unwrap(); + let instance_name = String::from_utf8(batch.instance.clone()).unwrap(); let benchmark_data = get_benchmark_data( batch, storage_info, @@ -166,7 +166,7 @@ fn map_results( worst_case_map_values, additional_trie_layers, ); - let pallet_benchmarks = all_benchmarks.entry((pallet_string, instance_string)).or_default(); + let pallet_benchmarks = all_benchmarks.entry((pallet_name, instance_name)).or_default(); pallet_benchmarks.push(benchmark_data); } Ok(all_benchmarks) @@ -571,19 +571,22 @@ pub(crate) fn process_storage_results( let mut prefix_result = result.clone(); let key_info = storage_info_map.get(&prefix); + let pallet_name = match key_info { + Some(k) => String::from_utf8(k.pallet_name.clone()).expect("encoded from string"), + None => "".to_string(), + }; + let storage_name = match key_info { + Some(k) => String::from_utf8(k.storage_name.clone()).expect("encoded from string"), + None => "".to_string(), + }; let max_size = key_info.and_then(|k| k.max_size); let override_pov_mode = match key_info { - Some(StorageInfo { pallet_name, storage_name, .. }) => { - let pallet_name = - String::from_utf8(pallet_name.clone()).expect("encoded from string"); - let storage_name = - String::from_utf8(storage_name.clone()).expect("encoded from string"); - + Some(_) => { // Is there an override for the storage key? - pov_modes.get(&(pallet_name.clone(), storage_name)).or( + pov_modes.get(&(pallet_name.clone(), storage_name.clone())).or( // .. or for the storage prefix? - pov_modes.get(&(pallet_name, "ALL".to_string())).or( + pov_modes.get(&(pallet_name.clone(), "ALL".to_string())).or( // .. or for the benchmark? pov_modes.get(&("ALL".to_string(), "ALL".to_string())), ), @@ -662,15 +665,10 @@ pub(crate) fn process_storage_results( // writes. if !is_prefix_identified { match key_info { - Some(key_info) => { + Some(_) => { let comment = format!( "Storage: `{}::{}` (r:{} w:{})", - String::from_utf8(key_info.pallet_name.clone()) - .expect("encoded from string"), - String::from_utf8(key_info.storage_name.clone()) - .expect("encoded from string"), - reads, - writes, + pallet_name, storage_name, reads, writes, ); comments.push(comment) }, @@ -698,11 +696,7 @@ pub(crate) fn process_storage_results( ) { Some(new_pov) => { let comment = format!( - "Proof: `{}::{}` (`max_values`: {:?}, `max_size`: {:?}, added: {}, mode: `{:?}`)", - String::from_utf8(key_info.pallet_name.clone()) - .expect("encoded from string"), - String::from_utf8(key_info.storage_name.clone()) - .expect("encoded from string"), + "Proof: `{pallet_name}::{storage_name}` (`max_values`: {:?}, `max_size`: {:?}, added: {}, mode: `{:?}`)", key_info.max_values, key_info.max_size, new_pov, @@ -711,13 +705,9 @@ pub(crate) fn process_storage_results( comments.push(comment) }, None => { - let pallet = String::from_utf8(key_info.pallet_name.clone()) - .expect("encoded from string"); - let item = String::from_utf8(key_info.storage_name.clone()) - .expect("encoded from string"); let comment = format!( "Proof: `{}::{}` (`max_values`: {:?}, `max_size`: {:?}, mode: `{:?}`)", - pallet, item, key_info.max_values, key_info.max_size, + pallet_name, storage_name, key_info.max_values, key_info.max_size, used_pov_mode, ); comments.push(comment); diff --git a/substrate/utils/frame/benchmarking-cli/src/storage/write.rs b/substrate/utils/frame/benchmarking-cli/src/storage/write.rs index 65941497bda41f94b1230f73d59b187895cee4d9..4fa56b6e818f8d90103eb080db2e071ba2a240eb 100644 --- a/substrate/utils/frame/benchmarking-cli/src/storage/write.rs +++ b/substrate/utils/frame/benchmarking-cli/src/storage/write.rs @@ -57,7 +57,7 @@ impl StorageCmd { let best_hash = client.usage_info().chain.best_hash; let header = client.header(best_hash)?.ok_or("Header not found")?; let original_root = *header.state_root(); - let trie = DbStateBuilder::::new(storage.clone(), original_root).build(); + let trie = DbStateBuilder::>::new(storage.clone(), original_root).build(); info!("Preparing keys from block {}", best_hash); // Load all KV pairs and randomly shuffle them. @@ -189,7 +189,7 @@ fn convert_tx( /// if `child_info` exist then it means this is a child tree key fn measure_write( db: Arc>, - trie: &DbState, + trie: &DbState>, key: Vec, new_v: Vec, version: StateVersion, @@ -220,7 +220,7 @@ fn measure_write( /// if `child_info` exist then it means this is a child tree key fn check_new_value( db: Arc>, - trie: &DbState, + trie: &DbState>, key: &Vec, new_v: &Vec, version: StateVersion, diff --git a/substrate/utils/frame/frame-utilities-cli/Cargo.toml b/substrate/utils/frame/frame-utilities-cli/Cargo.toml index 87e3a50753f734950791f617063e03f418ab4390..919016b2d8f09f4327ed34e4dc1a7d0fe9b3fe6b 100644 --- a/substrate/utils/frame/frame-utilities-cli/Cargo.toml +++ b/substrate/utils/frame/frame-utilities-cli/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" workspace = true [dependencies] -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } frame-support = { path = "../../../frame/support" } frame-system = { path = "../../../frame/system" } sc-cli = { path = "../../../client/cli" } diff --git a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml index b35e06879df7ebb493ea09a9d4be2b32b3e6128c..68d4733614c1521c285109aa7d8e3c8e9b8ea188 100644 --- a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -17,4 +17,4 @@ kitchensink-runtime = { path = "../../../../bin/node/runtime" } generate-bags = { path = ".." } # third-party -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml index fead2d20c7054772f6c941d4935932203f2d6c9c..61e0a861ee0ebd847a54c46280d5c3c10fce0ad2 100644 --- a/substrate/utils/frame/remote-externalities/Cargo.toml +++ b/substrate/utils/frame/remote-externalities/Cargo.toml @@ -15,10 +15,10 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["http-client"] } +jsonrpsee = { version = "0.22", features = ["http-client"] } codec = { package = "parity-scale-codec", version = "3.6.1" } -log = "0.4.17" -serde = "1.0.195" +log = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } sp-core = { path = "../../../primitives/core" } sp-crypto-hashing = { path = "../../../primitives/crypto/hashing" } sp-state-machine = { path = "../../../primitives/state-machine" } @@ -27,7 +27,7 @@ sp-runtime = { path = "../../../primitives/runtime" } tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] } substrate-rpc-client = { path = "../rpc/client" } futures = "0.3" -indicatif = "0.17.3" +indicatif = "0.17.7" spinners = "4.1.0" tokio-retry = "0.3.0" diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs index 47c0508485cd367da4627bdbca446c52e5974f45..c7399468da9da7e9e0d692ec659db3076a2d5a79 100644 --- a/substrate/utils/frame/remote-externalities/src/lib.rs +++ b/substrate/utils/frame/remote-externalities/src/lib.rs @@ -1263,7 +1263,11 @@ mod tests { #[cfg(all(test, feature = "remote-test"))] mod remote_tests { use super::test_prelude::*; - use std::os::unix::fs::MetadataExt; + use std::{env, os::unix::fs::MetadataExt}; + + fn endpoint() -> String { + env::var("TEST_WS").unwrap_or_else(|_| DEFAULT_HTTP_ENDPOINT.to_string()) + } #[tokio::test] async fn state_version_is_kept_and_can_be_altered() { @@ -1273,6 +1277,7 @@ mod remote_tests { // first, build a snapshot. let ext = Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1314,6 +1319,7 @@ mod remote_tests { // first, build a snapshot. let ext = Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1341,6 +1347,7 @@ mod remote_tests { // create an ext with children keys let child_ext = Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: true, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1353,6 +1360,7 @@ mod remote_tests { // create an ext without children keys let ext = Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1378,6 +1386,7 @@ mod remote_tests { .mode(Mode::OfflineOrElseOnline( OfflineConfig { state_snapshot: SnapshotConfig::new(CACHE) }, OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, state_snapshot: Some(SnapshotConfig::new(CACHE)), @@ -1419,6 +1428,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned()], child_trie: false, ..Default::default() @@ -1434,6 +1444,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), pallets: vec!["Proxy".to_owned(), "Multisig".to_owned()], child_trie: false, ..Default::default() @@ -1451,6 +1462,7 @@ mod remote_tests { Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), state_snapshot: Some(SnapshotConfig::new(CACHE)), pallets: vec!["Proxy".to_owned()], child_trie: false, @@ -1480,6 +1492,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), state_snapshot: Some(SnapshotConfig::new(CACHE)), pallets: vec!["Crowdloan".to_owned()], child_trie: true, @@ -1511,7 +1524,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: std::option_env!("TEST_WS").unwrap().to_owned().into(), + transport: endpoint().clone().into(), pallets: vec!["Staking".to_owned()], child_trie: false, ..Default::default() @@ -1530,7 +1543,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - transport: std::option_env!("TEST_WS").unwrap().to_owned().into(), + transport: endpoint().clone().into(), ..Default::default() })) .build() @@ -1543,9 +1556,10 @@ mod remote_tests { async fn can_fetch_in_parallel() { init_logger(); - let uri = String::from("wss://kusama-bridge-hub-rpc.polkadot.io:443"); - let mut builder = Builder::::new() - .mode(Mode::Online(OnlineConfig { transport: uri.into(), ..Default::default() })); + let mut builder = Builder::::new().mode(Mode::Online(OnlineConfig { + transport: endpoint().clone().into(), + ..Default::default() + })); builder.init_remote_client().await.unwrap(); let at = builder.as_online().at.unwrap(); diff --git a/substrate/utils/frame/rpc/client/Cargo.toml b/substrate/utils/frame/rpc/client/Cargo.toml index a97bc77b00f531d9dd0fc816a04a59a4ea73142f..b51e3f44f4e6457ee2f9e12dedace27728b96ac4 100644 --- a/substrate/utils/frame/rpc/client/Cargo.toml +++ b/substrate/utils/frame/rpc/client/Cargo.toml @@ -15,12 +15,12 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.20.3", features = ["ws-client"] } +jsonrpsee = { version = "0.22", features = ["ws-client"] } sc-rpc-api = { path = "../../../../client/rpc-api" } async-trait = "0.1.74" -serde = "1" +serde = { workspace = true, default-features = true } sp-runtime = { path = "../../../../primitives/runtime" } -log = "0.4" +log = { workspace = true, default-features = true } [dev-dependencies] tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread", "sync"] } diff --git a/substrate/utils/frame/rpc/client/src/lib.rs b/substrate/utils/frame/rpc/client/src/lib.rs index 9349ee2d357b2b8c608e722ea0100b790c509991..221f260b156699f012e17d5eecd8c311066d2902 100644 --- a/substrate/utils/frame/rpc/client/src/lib.rs +++ b/substrate/utils/frame/rpc/client/src/lib.rs @@ -44,9 +44,9 @@ use std::collections::VecDeque; pub use jsonrpsee::{ core::{ - client::{ClientT, Subscription, SubscriptionClientT}, + client::{ClientT, Error, Subscription, SubscriptionClientT}, params::BatchRequestBuilder, - Error, RpcResult, + RpcResult, }, rpc_params, ws_client::{WsClient, WsClientBuilder}, diff --git a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 83afff94a8dc06defc2abb90c4626d9acb633af7..f9a45e21ce13d3690c1efe366e1d0f480f0f9b8f 100644 --- a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -17,13 +17,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } -serde = { version = "1", features = ["derive"] } +serde = { features = ["derive"], workspace = true, default-features = true } sp-core = { path = "../../../../primitives/core" } sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } trie-db = "0.28.0" -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } + +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } # Substrate Dependencies sc-client-api = { path = "../../../../client/api" } @@ -31,4 +32,4 @@ sc-rpc-api = { path = "../../../../client/rpc-api" } sp-runtime = { path = "../../../../primitives/runtime" } [dev-dependencies] -serde_json = "1.0.111" +serde_json = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/rpc/support/Cargo.toml b/substrate/utils/frame/rpc/support/Cargo.toml index 3750e3272c90d56cc47b31b75a262477cf410360..2e4bb6a105784fafecd1344a909e69dbcbfa0e3f 100644 --- a/substrate/utils/frame/rpc/support/Cargo.toml +++ b/substrate/utils/frame/rpc/support/Cargo.toml @@ -16,15 +16,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["jsonrpsee-types"] } -serde = "1" +jsonrpsee = { version = "0.22", features = ["jsonrpsee-types"] } +serde = { workspace = true, default-features = true } frame-support = { path = "../../../../frame/support" } sc-rpc-api = { path = "../../../../client/rpc-api" } sp-storage = { path = "../../../../primitives/storage" } [dev-dependencies] scale-info = "2.10.0" -jsonrpsee = { version = "0.20.3", features = ["jsonrpsee-types", "ws-client"] } +jsonrpsee = { version = "0.22", features = ["jsonrpsee-types", "ws-client"] } tokio = "1.22.0" sp-core = { path = "../../../../primitives/core" } sp-runtime = { path = "../../../../primitives/runtime" } diff --git a/substrate/utils/frame/rpc/support/src/lib.rs b/substrate/utils/frame/rpc/support/src/lib.rs index e3ccbd6965893ed81b9698e4ff0b4caebba3830c..8280c46aadf2629c1f2b1efccfa98d2c7a5f5bdf 100644 --- a/substrate/utils/frame/rpc/support/src/lib.rs +++ b/substrate/utils/frame/rpc/support/src/lib.rs @@ -23,7 +23,7 @@ use codec::{DecodeAll, FullCodec, FullEncode}; use core::marker::PhantomData; use frame_support::storage::generator::{StorageDoubleMap, StorageMap, StorageValue}; -use jsonrpsee::core::Error as RpcError; +use jsonrpsee::core::ClientError as RpcError; use sc_rpc_api::state::StateApiClient; use serde::{de::DeserializeOwned, Serialize}; use sp_storage::{StorageData, StorageKey}; @@ -31,7 +31,7 @@ use sp_storage::{StorageData, StorageKey}; /// A typed query on chain state usable from an RPC client. /// /// ```no_run -/// # use jsonrpsee::core::Error as RpcError; +/// # use jsonrpsee::core::ClientError as RpcError; /// # use jsonrpsee::ws_client::WsClientBuilder; /// # use codec::Encode; /// # use frame_support::{construct_runtime, derive_impl, traits::ConstU32}; diff --git a/substrate/utils/frame/rpc/system/Cargo.toml b/substrate/utils/frame/rpc/system/Cargo.toml index 3f06ffe2bc2ee4ce5e96cb8e1e25bb7affff1779..f9a84a01af82f9cf4746e003990e43cb2515a7bc 100644 --- a/substrate/utils/frame/rpc/system/Cargo.toml +++ b/substrate/utils/frame/rpc/system/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.6.1" } -jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] } +jsonrpsee = { version = "0.22", features = ["client-core", "macros", "server"] } futures = "0.3.21" -log = "0.4.17" +log = { workspace = true, default-features = true } frame-system-rpc-runtime-api = { path = "../../../../frame/system/rpc/runtime-api" } sc-rpc-api = { path = "../../../../client/rpc-api" } sc-transaction-pool-api = { path = "../../../../client/transaction-pool/api" } diff --git a/substrate/utils/frame/try-runtime/cli/Cargo.toml b/substrate/utils/frame/try-runtime/cli/Cargo.toml index 1550a2cec4b054a5d8b9dc3874855dc705cc5336..d123fc5f62dc5848dc560f1eca37523435ebb331 100644 --- a/substrate/utils/frame/try-runtime/cli/Cargo.toml +++ b/substrate/utils/frame/try-runtime/cli/Cargo.toml @@ -38,12 +38,12 @@ frame-try-runtime = { path = "../../../../frame/try-runtime", optional = true } substrate-rpc-client = { path = "../../rpc/client" } async-trait = "0.1.74" -clap = { version = "4.4.18", features = ["derive"] } +clap = { version = "4.5.1", features = ["derive"] } hex = { version = "0.4.3", default-features = false } -log = "0.4.17" +log = { workspace = true, default-features = true } parity-scale-codec = "3.6.1" -serde = "1.0.195" -serde_json = "1.0.111" +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } zstd = { version = "0.12.4", default-features = false } [dev-dependencies] diff --git a/substrate/utils/prometheus/Cargo.toml b/substrate/utils/prometheus/Cargo.toml index 5ce943fbc598faf21af32f86edb81d7abe187a91..36527ac6183bb921266023320345afb9f6a89246 100644 --- a/substrate/utils/prometheus/Cargo.toml +++ b/substrate/utils/prometheus/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] } -log = "0.4.17" +log = { workspace = true, default-features = true } prometheus = { version = "0.13.0", default-features = false } -thiserror = "1.0" +thiserror = { workspace = true } tokio = { version = "1.22.0", features = ["parking_lot"] } [dev-dependencies] diff --git a/substrate/utils/wasm-builder/Cargo.toml b/substrate/utils/wasm-builder/Cargo.toml index f01adbc03d6cca48e28150a2d30f48bfa9a54ff7..7abd1a202848f6159ef33bb9efe06de61cac797c 100644 --- a/substrate/utils/wasm-builder/Cargo.toml +++ b/substrate/utils/wasm-builder/Cargo.toml @@ -26,3 +26,4 @@ sp-maybe-compressed-blob = { path = "../../primitives/maybe-compressed-blob" } filetime = "0.2.16" wasm-opt = "0.116" parity-wasm = "0.45" +polkavm-linker = { workspace = true } diff --git a/substrate/utils/wasm-builder/src/builder.rs b/substrate/utils/wasm-builder/src/builder.rs index 9c1655d85623b3c0b8568699b923af9390837c16..d2aaff448bc5fdc2f2c1cb73774e0d7603ae90db 100644 --- a/substrate/utils/wasm-builder/src/builder.rs +++ b/substrate/utils/wasm-builder/src/builder.rs @@ -21,6 +21,8 @@ use std::{ process, }; +use crate::RuntimeTarget; + /// Returns the manifest dir from the `CARGO_MANIFEST_DIR` env. fn get_manifest_dir() -> PathBuf { env::var("CARGO_MANIFEST_DIR") @@ -49,6 +51,8 @@ impl WasmBuilderSelectProject { project_cargo_toml: get_manifest_dir().join("Cargo.toml"), features_to_enable: Vec::new(), disable_runtime_version_section_check: false, + export_heap_base: false, + import_memory: false, } } @@ -65,6 +69,8 @@ impl WasmBuilderSelectProject { project_cargo_toml: path, features_to_enable: Vec::new(), disable_runtime_version_section_check: false, + export_heap_base: false, + import_memory: false, }) } else { Err("Project path must point to the `Cargo.toml` of the project") @@ -97,6 +103,11 @@ pub struct WasmBuilder { features_to_enable: Vec, /// Should the builder not check that the `runtime_version` section exists in the wasm binary? disable_runtime_version_section_check: bool, + + /// Whether `__heap_base` should be exported (WASM-only). + export_heap_base: bool, + /// Whether `--import-memory` should be added to the link args (WASM-only). + import_memory: bool, } impl WasmBuilder { @@ -109,7 +120,7 @@ impl WasmBuilder { /// /// This adds `-Clink-arg=--export=__heap_base` to `RUST_FLAGS`. pub fn export_heap_base(mut self) -> Self { - self.rust_flags.push("-Clink-arg=--export=__heap_base".into()); + self.export_heap_base = true; self } @@ -127,7 +138,7 @@ impl WasmBuilder { /// /// This adds `-C link-arg=--import-memory` to `RUST_FLAGS`. pub fn import_memory(mut self) -> Self { - self.rust_flags.push("-C link-arg=--import-memory".into()); + self.import_memory = true; self } @@ -159,7 +170,18 @@ impl WasmBuilder { } /// Build the WASM binary. - pub fn build(self) { + pub fn build(mut self) { + let target = crate::runtime_target(); + if target == RuntimeTarget::Wasm { + if self.export_heap_base { + self.rust_flags.push("-Clink-arg=--export=__heap_base".into()); + } + + if self.import_memory { + self.rust_flags.push("-C link-arg=--import-memory".into()); + } + } + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is set by cargo!")); let file_path = out_dir.join(self.file_name.clone().unwrap_or_else(|| "wasm_binary.rs".into())); @@ -175,6 +197,7 @@ impl WasmBuilder { } build_project( + target, file_path, self.project_cargo_toml, self.rust_flags.into_iter().map(|f| format!("{} ", f)).collect(), @@ -248,6 +271,7 @@ fn generate_rerun_if_changed_instructions() { /// `check_for_runtime_version_section` - Should the wasm binary be checked for the /// `runtime_version` section? fn build_project( + target: RuntimeTarget, file_name: PathBuf, project_cargo_toml: PathBuf, default_rustflags: String, @@ -255,7 +279,7 @@ fn build_project( wasm_binary_name: Option, check_for_runtime_version_section: bool, ) { - let cargo_cmd = match crate::prerequisites::check() { + let cargo_cmd = match crate::prerequisites::check(target) { Ok(cmd) => cmd, Err(err_msg) => { eprintln!("{}", err_msg); @@ -264,6 +288,7 @@ fn build_project( }; let (wasm_binary, bloaty) = crate::wasm_project::create_and_compile( + target, &project_cargo_toml, &default_rustflags, cargo_cmd, diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index ec85fd1ffddbf8f9aa54aec0e840830c99861a11..5cde48c0950b341cd36f3a5594620be683c799ea 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -113,6 +113,7 @@ //! wasm32-unknown-unknown --toolchain nightly-2020-02-20`. use std::{ + collections::BTreeSet, env, fs, io::BufRead, path::{Path, PathBuf}, @@ -164,6 +165,9 @@ const WASM_BUILD_WORKSPACE_HINT: &str = "WASM_BUILD_WORKSPACE_HINT"; /// Environment variable to set whether we'll build `core`/`std`. const WASM_BUILD_STD: &str = "WASM_BUILD_STD"; +/// The target to use for the runtime. Valid values are `wasm` (default) or `riscv`. +const RUNTIME_TARGET: &str = "SUBSTRATE_RUNTIME_TARGET"; + /// Write to the given `file` if the `content` is different. fn write_file_if_changed(file: impl AsRef, content: impl AsRef) { if fs::read_to_string(file.as_ref()).ok().as_deref() != Some(content.as_ref()) { @@ -185,7 +189,7 @@ fn copy_file_if_changed(src: PathBuf, dst: PathBuf) { } /// Get a cargo command that should be used to invoke the compilation. -fn get_cargo_command() -> CargoCommand { +fn get_cargo_command(target: RuntimeTarget) -> CargoCommand { let env_cargo = CargoCommand::new(&env::var("CARGO").expect("`CARGO` env variable is always set by cargo")); let default_cargo = CargoCommand::new("cargo"); @@ -196,35 +200,33 @@ fn get_cargo_command() -> CargoCommand { wasm_toolchain.map(|t| CargoCommand::new_with_args("rustup", &["run", &t, "cargo"])) { cmd - } else if env_cargo.supports_substrate_wasm_env() { + } else if env_cargo.supports_substrate_runtime_env(target) { env_cargo - } else if default_cargo.supports_substrate_wasm_env() { + } else if default_cargo.supports_substrate_runtime_env(target) { default_cargo } else { // If no command before provided us with a cargo that supports our Substrate wasm env, we // try to search one with rustup. If that fails as well, we return the default cargo and let // the prequisities check fail. - get_rustup_command().unwrap_or(default_cargo) + get_rustup_command(target).unwrap_or(default_cargo) } } -/// Get the newest rustup command that supports our Substrate wasm env. +/// Get the newest rustup command that supports compiling a runtime. /// /// Stable versions are always favored over nightly versions even if the nightly versions are /// newer. -fn get_rustup_command() -> Option { - let host = format!("-{}", env::var("HOST").expect("`HOST` is always set by cargo")); - +fn get_rustup_command(target: RuntimeTarget) -> Option { let output = Command::new("rustup").args(&["toolchain", "list"]).output().ok()?.stdout; let lines = output.as_slice().lines(); let mut versions = Vec::new(); for line in lines.filter_map(|l| l.ok()) { - let rustup_version = line.trim_end_matches(&host); - + // Split by a space to get rid of e.g. " (default)" at the end. + let rustup_version = line.split(" ").next().unwrap(); let cmd = CargoCommand::new_with_args("rustup", &["run", &rustup_version, "cargo"]); - if !cmd.supports_substrate_wasm_env() { + if !cmd.supports_substrate_runtime_env(target) { continue } @@ -247,22 +249,26 @@ struct CargoCommand { program: String, args: Vec, version: Option, + target_list: Option>, } impl CargoCommand { fn new(program: &str) -> Self { let version = Self::extract_version(program, &[]); + let target_list = Self::extract_target_list(program, &[]); - CargoCommand { program: program.into(), args: Vec::new(), version } + CargoCommand { program: program.into(), args: Vec::new(), version, target_list } } fn new_with_args(program: &str, args: &[&str]) -> Self { let version = Self::extract_version(program, args); + let target_list = Self::extract_target_list(program, args); CargoCommand { program: program.into(), args: args.iter().map(ToString::to_string).collect(), version, + target_list, } } @@ -283,6 +289,23 @@ impl CargoCommand { Version::extract(&version) } + fn extract_target_list(program: &str, args: &[&str]) -> Option> { + // This is technically an unstable option, but we don't care because we only need this + // to build RISC-V runtimes, and those currently require a specific nightly toolchain + // anyway, so it's totally fine for this to fail in other cases. + let list = Command::new(program) + .args(args) + .args(&["rustc", "-Z", "unstable-options", "--print", "target-list"]) + // Make sure if we're called from within a `build.rs` the host toolchain won't override + // a rustup toolchain we've picked. + .env_remove("RUSTC") + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok())?; + + Some(list.trim().split("\n").map(ToString::to_string).collect()) + } + /// Returns the version of this cargo command or `None` if it failed to extract the version. fn version(&self) -> Option { self.version @@ -294,12 +317,29 @@ impl CargoCommand { env::var("RUSTC_BOOTSTRAP").is_ok() } + /// Check if the supplied cargo command supports our runtime environment. + fn supports_substrate_runtime_env(&self, target: RuntimeTarget) -> bool { + match target { + RuntimeTarget::Wasm => self.supports_substrate_runtime_env_wasm(), + RuntimeTarget::Riscv => self.supports_substrate_runtime_env_riscv(), + } + } + + /// Check if the supplied cargo command supports our RISC-V runtime environment. + fn supports_substrate_runtime_env_riscv(&self) -> bool { + let Some(target_list) = self.target_list.as_ref() else { return false }; + // This is our custom target which currently doesn't exist on any upstream toolchain, + // so if it exists it's guaranteed to be our custom toolchain and have have everything + // we need, so any further version checks are unnecessary at this point. + target_list.contains("riscv32ema-unknown-none-elf") + } + /// Check if the supplied cargo command supports our Substrate wasm environment. /// /// This means that either the cargo version is at minimum 1.68.0 or this is a nightly cargo. /// /// Assumes that cargo version matches the rustc version. - fn supports_substrate_wasm_env(&self) -> bool { + fn supports_substrate_runtime_env_wasm(&self) -> bool { // `RUSTC_BOOTSTRAP` tells a stable compiler to behave like a nightly. So, when this env // variable is set, we can assume that whatever rust compiler we have, it is a nightly // compiler. For "more" information, see: @@ -365,5 +405,48 @@ fn get_bool_environment_variable(name: &str) -> Option { /// Returns whether we need to also compile the standard library when compiling the runtime. fn build_std_required() -> bool { - crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or(true) + let default = runtime_target() == RuntimeTarget::Wasm; + + crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or(default) +} + +#[derive(Copy, Clone, PartialEq, Eq)] +enum RuntimeTarget { + Wasm, + Riscv, +} + +impl RuntimeTarget { + fn rustc_target(self) -> &'static str { + match self { + RuntimeTarget::Wasm => "wasm32-unknown-unknown", + RuntimeTarget::Riscv => "riscv32ema-unknown-none-elf", + } + } + + fn build_subdirectory(self) -> &'static str { + // Keep the build directories separate so that when switching between + // the targets we won't trigger unnecessary rebuilds. + match self { + RuntimeTarget::Wasm => "wbuild", + RuntimeTarget::Riscv => "rbuild", + } + } +} + +fn runtime_target() -> RuntimeTarget { + let Some(value) = env::var_os(RUNTIME_TARGET) else { + return RuntimeTarget::Wasm; + }; + + if value == "wasm" { + RuntimeTarget::Wasm + } else if value == "riscv" { + RuntimeTarget::Riscv + } else { + build_helper::warning!( + "the '{RUNTIME_TARGET}' environment variable has an invalid value; it must be either 'wasm' or 'riscv'" + ); + std::process::exit(1); + } } diff --git a/substrate/utils/wasm-builder/src/prerequisites.rs b/substrate/utils/wasm-builder/src/prerequisites.rs index 99eb6ee1f18fc325fab0ce30b1a4c706da6faeb2..a601e3210dd0c2b89beffa1d5d6b788cb35fd6a3 100644 --- a/substrate/utils/wasm-builder/src/prerequisites.rs +++ b/substrate/utils/wasm-builder/src/prerequisites.rs @@ -15,14 +15,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{write_file_if_changed, CargoCommand, CargoCommandVersioned}; +use crate::{write_file_if_changed, CargoCommand, CargoCommandVersioned, RuntimeTarget}; use console::style; -use std::{fs, path::Path}; +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; + use tempfile::tempdir; -/// Print an error message. -fn print_error_message(message: &str) -> String { +/// Colorizes an error message, if color output is enabled. +fn colorize_error_message(message: &str) -> String { if super::color_output_enabled() { style(message).red().bold().to_string() } else { @@ -30,121 +35,165 @@ fn print_error_message(message: &str) -> String { } } +/// Colorizes an auxiliary message, if color output is enabled. +fn colorize_aux_message(message: &str) -> String { + if super::color_output_enabled() { + style(message).yellow().bold().to_string() + } else { + message.into() + } +} + /// Checks that all prerequisites are installed. /// /// Returns the versioned cargo command on success. -pub(crate) fn check() -> Result { - let cargo_command = crate::get_cargo_command(); - - if !cargo_command.supports_substrate_wasm_env() { - return Err(print_error_message( - "Cannot compile the WASM runtime: no compatible Rust compiler found!\n\ - Install at least Rust 1.68.0 or a recent nightly version.", - )) - } +pub(crate) fn check(target: RuntimeTarget) -> Result { + let cargo_command = crate::get_cargo_command(target); + match target { + RuntimeTarget::Wasm => { + if !cargo_command.supports_substrate_runtime_env(target) { + return Err(colorize_error_message( + "Cannot compile a WASM runtime: no compatible Rust compiler found!\n\ + Install at least Rust 1.68.0 or a recent nightly version.", + )); + } - check_wasm_toolchain_installed(cargo_command) + check_wasm_toolchain_installed(cargo_command) + }, + RuntimeTarget::Riscv => { + if !cargo_command.supports_substrate_runtime_env(target) { + return Err(colorize_error_message( + "Cannot compile a RISC-V runtime: no compatible Rust compiler found!\n\ + Install a toolchain from here and try again: https://github.com/paritytech/rustc-rv32e-toolchain/", + )); + } + + let dummy_crate = DummyCrate::new(&cargo_command, target); + let version = dummy_crate.get_rustc_version(); + Ok(CargoCommandVersioned::new(cargo_command, version)) + }, + } } -/// Creates a minimal dummy crate at the given path and returns the manifest path. -fn create_minimal_crate(project_dir: &Path) -> std::path::PathBuf { - fs::create_dir_all(project_dir.join("src")).expect("Creating src dir does not fail; qed"); - - let manifest_path = project_dir.join("Cargo.toml"); - write_file_if_changed( - &manifest_path, - r#" - [package] - name = "wasm-test" - version = "1.0.0" - edition = "2021" - - [workspace] - "#, - ); - - write_file_if_changed(project_dir.join("src/main.rs"), "fn main() {}"); - manifest_path +struct DummyCrate<'a> { + cargo_command: &'a CargoCommand, + temp: tempfile::TempDir, + manifest_path: PathBuf, + target: RuntimeTarget, } -fn check_wasm_toolchain_installed( - cargo_command: CargoCommand, -) -> Result { - let temp = tempdir().expect("Creating temp dir does not fail; qed"); - let manifest_path = create_minimal_crate(temp.path()).display().to_string(); +impl<'a> DummyCrate<'a> { + /// Creates a minimal dummy crate. + fn new(cargo_command: &'a CargoCommand, target: RuntimeTarget) -> Self { + let temp = tempdir().expect("Creating temp dir does not fail; qed"); + let project_dir = temp.path(); + fs::create_dir_all(project_dir.join("src")).expect("Creating src dir does not fail; qed"); + + let manifest_path = project_dir.join("Cargo.toml"); + write_file_if_changed( + &manifest_path, + r#" + [package] + name = "dummy-crate" + version = "1.0.0" + edition = "2021" + + [workspace] + "#, + ); + + write_file_if_changed(project_dir.join("src/main.rs"), "fn main() {}"); + DummyCrate { cargo_command, temp, manifest_path, target } + } - let prepare_command = |subcommand| { - let mut cmd = cargo_command.command(); + fn prepare_command(&self, subcommand: &str) -> Command { + let mut cmd = self.cargo_command.command(); // Chdir to temp to avoid including project's .cargo/config.toml // by accident - it can happen in some CI environments. - cmd.current_dir(&temp); - cmd.args(&[ - subcommand, - "--target=wasm32-unknown-unknown", - "--manifest-path", - &manifest_path, - ]); + cmd.current_dir(&self.temp); + cmd.arg(subcommand) + .arg(format!("--target={}", self.target.rustc_target())) + .args(&["--manifest-path", &self.manifest_path.display().to_string()]); if super::color_output_enabled() { cmd.arg("--color=always"); } // manually set the `CARGO_TARGET_DIR` to prevent a cargo deadlock - let target_dir = temp.path().join("target").display().to_string(); + let target_dir = self.temp.path().join("target").display().to_string(); cmd.env("CARGO_TARGET_DIR", &target_dir); // Make sure the host's flags aren't used here, e.g. if an alternative linker is specified // in the RUSTFLAGS then the check we do here will break unless we clear these. cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); cmd.env_remove("RUSTFLAGS"); + // Make sure if we're called from within a `build.rs` the host toolchain won't override a + // rustup toolchain we've picked. + cmd.env_remove("RUSTC"); cmd - }; - - let err_msg = - print_error_message("Rust WASM toolchain is not properly installed; please install it!"); - let build_result = prepare_command("build").output().map_err(|_| err_msg.clone())?; - if !build_result.status.success() { - return match String::from_utf8(build_result.stderr) { - Ok(ref err) if err.contains("the `wasm32-unknown-unknown` target may not be installed") => - Err(print_error_message("Cannot compile the WASM runtime: the `wasm32-unknown-unknown` target is not installed!\n\ - You can install it with `rustup target add wasm32-unknown-unknown` if you're using `rustup`.")), + } - // Apparently this can happen when we're running on a non Tier 1 platform. - Ok(ref err) if err.contains("linker `rust-lld` not found") => - Err(print_error_message("Cannot compile the WASM runtime: `rust-lld` not found!")), + fn get_rustc_version(&self) -> String { + let mut run_cmd = self.prepare_command("rustc"); + run_cmd.args(&["-q", "--", "--version"]); + run_cmd + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()) + .unwrap_or_else(|| "unknown rustc version".into()) + } - Ok(ref err) => Err(format!( - "{}\n\n{}\n{}\n{}{}\n", - err_msg, - style("Further error information:").yellow().bold(), - style("-".repeat(60)).yellow().bold(), - err, - style("-".repeat(60)).yellow().bold(), - )), - - Err(_) => Err(err_msg), - }; + fn get_sysroot(&self) -> Option { + let mut sysroot_cmd = self.prepare_command("rustc"); + sysroot_cmd.args(&["-q", "--", "--print", "sysroot"]); + sysroot_cmd.output().ok().and_then(|o| String::from_utf8(o.stdout).ok()) } - let mut run_cmd = prepare_command("rustc"); - run_cmd.args(&["-q", "--", "--version"]); + fn try_build(&self) -> Result<(), Option> { + let Ok(result) = self.prepare_command("build").output() else { return Err(None) }; + if !result.status.success() { + return Err(Some(String::from_utf8_lossy(&result.stderr).into())); + } + Ok(()) + } +} - let version = run_cmd - .output() - .ok() - .and_then(|o| String::from_utf8(o.stdout).ok()) - .unwrap_or_else(|| "unknown rustc version".into()); +fn check_wasm_toolchain_installed( + cargo_command: CargoCommand, +) -> Result { + let dummy_crate = DummyCrate::new(&cargo_command, RuntimeTarget::Wasm); + + if let Err(error) = dummy_crate.try_build() { + let basic_error_message = colorize_error_message( + "Rust WASM toolchain is not properly installed; please install it!", + ); + return match error { + None => Err(basic_error_message), + Some(error) if error.contains("the `wasm32-unknown-unknown` target may not be installed") => { + Err(colorize_error_message("Cannot compile the WASM runtime: the `wasm32-unknown-unknown` target is not installed!\n\ + You can install it with `rustup target add wasm32-unknown-unknown` if you're using `rustup`.")) + }, + // Apparently this can happen when we're running on a non Tier 1 platform. + Some(ref error) if error.contains("linker `rust-lld` not found") => + Err(colorize_error_message("Cannot compile the WASM runtime: `rust-lld` not found!")), + Some(error) => Err(format!( + "{}\n\n{}\n{}\n{}{}\n", + basic_error_message, + colorize_aux_message("Further error information:"), + colorize_aux_message(&"-".repeat(60)), + error, + colorize_aux_message(&"-".repeat(60)), + )) + } + } + let version = dummy_crate.get_rustc_version(); if crate::build_std_required() { - let mut sysroot_cmd = prepare_command("rustc"); - sysroot_cmd.args(&["-q", "--", "--print", "sysroot"]); - if let Some(sysroot) = - sysroot_cmd.output().ok().and_then(|o| String::from_utf8(o.stdout).ok()) - { + if let Some(sysroot) = dummy_crate.get_sysroot() { let src_path = Path::new(sysroot.trim()).join("lib").join("rustlib").join("src").join("rust"); if !src_path.exists() { - return Err(print_error_message( + return Err(colorize_error_message( "Cannot compile the WASM runtime: no standard library sources found!\n\ You can install them with `rustup component add rust-src` if you're using `rustup`.", )) diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index ded6b2188b0b35c890ca9ad62ac658ee786ab553..9ffb5c72fd9593f60dfddb2ad6333a5d9887330b 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{write_file_if_changed, CargoCommandVersioned, OFFLINE}; +use crate::{write_file_if_changed, CargoCommandVersioned, RuntimeTarget, OFFLINE}; use build_helper::rerun_if_changed; use cargo_metadata::{DependencyKind, Metadata, MetadataCommand}; @@ -112,6 +112,7 @@ fn crate_metadata(cargo_manifest: &Path) -> Metadata { /// /// The path to the compact runtime binary and the bloaty runtime binary. pub(crate) fn create_and_compile( + target: RuntimeTarget, project_cargo_toml: &Path, default_rustflags: &str, cargo_cmd: CargoCommandVersioned, @@ -120,11 +121,12 @@ pub(crate) fn create_and_compile( check_for_runtime_version_section: bool, ) -> (Option, WasmBinaryBloaty) { let runtime_workspace_root = get_wasm_workspace_root(); - let runtime_workspace = runtime_workspace_root.join("wbuild"); + let runtime_workspace = runtime_workspace_root.join(target.build_subdirectory()); let crate_metadata = crate_metadata(project_cargo_toml); let project = create_project( + target, project_cargo_toml, &runtime_workspace, &crate_metadata, @@ -132,13 +134,58 @@ pub(crate) fn create_and_compile( features_to_enable, ); - let build_config = BuildConfiguration::detect(&project); + let build_config = BuildConfiguration::detect(target, &project); // Build the bloaty runtime blob - build_bloaty_blob(&build_config.blob_build_profile, &project, default_rustflags, cargo_cmd); + let raw_blob_path = build_bloaty_blob( + target, + &build_config.blob_build_profile, + &project, + default_rustflags, + cargo_cmd, + ); + + let (final_blob_binary, bloaty_blob_binary) = match target { + RuntimeTarget::Wasm => compile_wasm( + project_cargo_toml, + &project, + bloaty_blob_out_name_override, + check_for_runtime_version_section, + &build_config, + ), + RuntimeTarget::Riscv => { + let out_name = bloaty_blob_out_name_override + .unwrap_or_else(|| get_blob_name(target, project_cargo_toml)); + let out_path = project.join(format!("{out_name}.polkavm")); + fs::copy(raw_blob_path, &out_path).expect("copying the runtime blob should never fail"); + (None, WasmBinaryBloaty(out_path)) + }, + }; + + generate_rerun_if_changed_instructions( + project_cargo_toml, + &project, + &runtime_workspace, + final_blob_binary.as_ref(), + &bloaty_blob_binary, + ); + if let Err(err) = adjust_mtime(&bloaty_blob_binary, final_blob_binary.as_ref()) { + build_helper::warning!("Error while adjusting the mtime of the blob binaries: {}", err) + } + + (final_blob_binary, bloaty_blob_binary) +} + +fn compile_wasm( + project_cargo_toml: &Path, + project: &Path, + bloaty_blob_out_name_override: Option, + check_for_runtime_version_section: bool, + build_config: &BuildConfiguration, +) -> (Option, WasmBinaryBloaty) { // Get the name of the bloaty runtime blob. - let bloaty_blob_default_name = get_blob_name(project_cargo_toml); + let bloaty_blob_default_name = get_blob_name(RuntimeTarget::Wasm, project_cargo_toml); let bloaty_blob_out_name = bloaty_blob_out_name_override.unwrap_or_else(|| bloaty_blob_default_name.clone()); @@ -183,19 +230,6 @@ pub(crate) fn create_and_compile( }); let final_blob_binary = compact_compressed_blob_path.or(compact_blob_path); - - generate_rerun_if_changed_instructions( - project_cargo_toml, - &project, - &runtime_workspace, - final_blob_binary.as_ref(), - &bloaty_blob_binary, - ); - - if let Err(err) = adjust_mtime(&bloaty_blob_binary, final_blob_binary.as_ref()) { - build_helper::warning!("Error while adjusting the mtime of the blob binaries: {}", err) - } - (final_blob_binary, bloaty_blob_binary) } @@ -314,8 +348,12 @@ fn get_crate_name(cargo_manifest: &Path) -> String { } /// Returns the name for the blob binary. -fn get_blob_name(cargo_manifest: &Path) -> String { - get_crate_name(cargo_manifest).replace('-', "_") +fn get_blob_name(target: RuntimeTarget, cargo_manifest: &Path) -> String { + let crate_name = get_crate_name(cargo_manifest); + match target { + RuntimeTarget::Wasm => crate_name.replace('-', "_"), + RuntimeTarget::Riscv => crate_name, + } } /// Returns the root path of the wasm workspace. @@ -336,6 +374,7 @@ fn get_wasm_workspace_root() -> PathBuf { } fn create_project_cargo_toml( + target: RuntimeTarget, wasm_workspace: &Path, workspace_root_path: &Path, crate_name: &str, @@ -396,17 +435,18 @@ fn create_project_cargo_toml( } let mut package = Table::new(); - package.insert("name".into(), format!("{}-wasm", crate_name).into()); + package.insert("name".into(), format!("{}-blob", crate_name).into()); package.insert("version".into(), "1.0.0".into()); package.insert("edition".into(), "2021".into()); wasm_workspace_toml.insert("package".into(), package.into()); - let mut lib = Table::new(); - lib.insert("name".into(), wasm_binary.into()); - lib.insert("crate-type".into(), vec!["cdylib".to_string()].into()); - - wasm_workspace_toml.insert("lib".into(), lib.into()); + if target == RuntimeTarget::Wasm { + let mut lib = Table::new(); + lib.insert("name".into(), wasm_binary.into()); + lib.insert("crate-type".into(), vec!["cdylib".to_string()].into()); + wasm_workspace_toml.insert("lib".into(), lib.into()); + } let mut dependencies = Table::new(); @@ -422,6 +462,18 @@ fn create_project_cargo_toml( wasm_workspace_toml.insert("workspace".into(), Table::new().into()); + if target == RuntimeTarget::Riscv { + // This dependency currently doesn't compile under RISC-V, so patch it with our own fork. + // + // TODO: Remove this once a new version of `bitvec` (which uses a new version of `radium` + // which doesn't have this problem) is released on crates.io. + let patch = toml::toml! { + [crates-io] + radium = { git = "https://github.com/paritytech/radium-0.7-fork.git", rev = "a5da15a15c90fd169d661d206cf0db592487f52b" } + }; + wasm_workspace_toml.insert("patch".into(), patch.into()); + } + write_file_if_changed( wasm_workspace.join("Cargo.toml"), toml::to_string_pretty(&wasm_workspace_toml).expect("Wasm workspace toml is valid; qed"), @@ -527,6 +579,7 @@ fn has_runtime_wasm_feature_declared( /// /// The path to the created wasm project. fn create_project( + target: RuntimeTarget, project_cargo_toml: &Path, wasm_workspace: &Path, crate_metadata: &Metadata, @@ -535,7 +588,7 @@ fn create_project( ) -> PathBuf { let crate_name = get_crate_name(project_cargo_toml); let crate_path = project_cargo_toml.parent().expect("Parent path exists; qed"); - let wasm_binary = get_blob_name(project_cargo_toml); + let wasm_binary = get_blob_name(target, project_cargo_toml); let wasm_project_folder = wasm_workspace.join(&crate_name); fs::create_dir_all(wasm_project_folder.join("src")) @@ -552,6 +605,7 @@ fn create_project( enabled_features.extend(features_to_enable.into_iter()); create_project_cargo_toml( + target, &wasm_project_folder, workspace_root_path, &crate_name, @@ -560,10 +614,20 @@ fn create_project( enabled_features.into_iter(), ); - write_file_if_changed( - wasm_project_folder.join("src/lib.rs"), - "#![no_std] pub use wasm_project::*;", - ); + match target { + RuntimeTarget::Wasm => { + write_file_if_changed( + wasm_project_folder.join("src/lib.rs"), + "#![no_std] pub use wasm_project::*;", + ); + }, + RuntimeTarget::Riscv => { + write_file_if_changed( + wasm_project_folder.join("src/main.rs"), + "#![no_std] #![no_main] pub use wasm_project::*;", + ); + }, + } if let Some(crate_lock_file) = find_cargo_lock(project_cargo_toml) { // Use the `Cargo.lock` of the main project. @@ -641,14 +705,15 @@ impl BuildConfiguration { /// # Note /// /// Can be overriden by setting [`crate::WASM_BUILD_TYPE_ENV`]. - fn detect(wasm_project: &Path) -> Self { + fn detect(target: RuntimeTarget, wasm_project: &Path) -> Self { let (name, overriden) = if let Ok(name) = env::var(crate::WASM_BUILD_TYPE_ENV) { (name, true) } else { // First go backwards to the beginning of the target directory. - // Then go forwards to find the "wbuild" directory. + // Then go forwards to find the build subdirectory. // We need to go backwards first because when starting from the root there - // might be a chance that someone has a "wbuild" directory somewhere in the path. + // might be a chance that someone has a directory somewhere in the path with the same + // name. let name = wasm_project .components() .rev() @@ -656,9 +721,9 @@ impl BuildConfiguration { .collect::>() .iter() .rev() - .take_while(|c| c.as_os_str() != "wbuild") + .take_while(|c| c.as_os_str() != target.build_subdirectory()) .last() - .expect("We put the wasm project within a `target/.../wbuild` path; qed") + .expect("We put the runtime project within a `target/.../[rw]build` path; qed") .as_os_str() .to_str() .expect("All our profile directory names are ascii; qed") @@ -711,22 +776,34 @@ fn offline_build() -> bool { /// Build the project and create the bloaty runtime blob. fn build_bloaty_blob( + target: RuntimeTarget, blob_build_profile: &Profile, project: &Path, default_rustflags: &str, cargo_cmd: CargoCommandVersioned, -) { +) -> PathBuf { let manifest_path = project.join("Cargo.toml"); let mut build_cmd = cargo_cmd.command(); - let rustflags = format!( - "-C target-cpu=mvp -C target-feature=-sign-ext -C link-arg=--export-table {} {}", - default_rustflags, - env::var(crate::WASM_BUILD_RUSTFLAGS_ENV).unwrap_or_default(), - ); + let mut rustflags = String::new(); + match target { + RuntimeTarget::Wasm => { + rustflags.push_str( + "-C target-cpu=mvp -C target-feature=-sign-ext -C link-arg=--export-table ", + ); + }, + RuntimeTarget::Riscv => { + rustflags.push_str("-C target-feature=+lui-addi-fusion -C relocation-model=pie -C link-arg=--emit-relocs -C link-arg=--unique "); + }, + } + + rustflags.push_str(default_rustflags); + rustflags.push_str(" --cfg substrate_runtime "); + rustflags.push_str(&env::var(crate::WASM_BUILD_RUSTFLAGS_ENV).unwrap_or_default()); build_cmd - .args(&["rustc", "--target=wasm32-unknown-unknown"]) + .arg("rustc") + .arg(format!("--target={}", target.rustc_target())) .arg(format!("--manifest-path={}", manifest_path.display())) .env("RUSTFLAGS", rustflags) // Manually set the `CARGO_TARGET_DIR` to prevent a cargo deadlock (cargo locks a target dir @@ -737,6 +814,9 @@ fn build_bloaty_blob( // our own `RUSTFLAGS` and thus, we need to remove this. Otherwise cargo favors this // env variable. .env_remove("CARGO_ENCODED_RUSTFLAGS") + // Make sure if we're called from within a `build.rs` the host toolchain won't override a + // rustup toolchain we've picked. + .env_remove("RUSTC") // We don't want to call ourselves recursively .env(crate::SKIP_BUILD_ENV, ""); @@ -775,9 +855,55 @@ fn build_bloaty_blob( println!("{} {}", colorize_info_message("Using rustc version:"), cargo_cmd.rustc_version()); // Use `process::exit(1)` to have a clean error output. - if build_cmd.status().map(|s| s.success()).is_err() { + if !matches!(build_cmd.status().map(|s| s.success()), Ok(true)) { process::exit(1); } + + let blob_name = get_blob_name(target, &manifest_path); + let target_directory = project + .join("target") + .join(target.rustc_target()) + .join(blob_build_profile.directory()); + match target { + RuntimeTarget::Riscv => { + let elf_path = target_directory.join(&blob_name); + let elf_metadata = match elf_path.metadata() { + Ok(path) => path, + Err(error) => + panic!("internal error: couldn't read the metadata of {elf_path:?}: {error}"), + }; + + let polkavm_path = target_directory.join(format!("{}.polkavm", blob_name)); + if polkavm_path + .metadata() + .map(|polkavm_metadata| { + polkavm_metadata.modified().unwrap() < elf_metadata.modified().unwrap() + }) + .unwrap_or(true) + { + let blob_bytes = + std::fs::read(elf_path).expect("binary always exists after its built"); + + let mut config = polkavm_linker::Config::default(); + config.set_strip(true); // TODO: This shouldn't always be done. + + let program = match polkavm_linker::program_from_elf(config, &blob_bytes) { + Ok(program) => program, + Err(error) => { + println!("Failed to link the runtime blob; this is probably a bug!"); + println!("Linking error: {error}"); + process::exit(1); + }, + }; + + std::fs::write(&polkavm_path, program.as_bytes()) + .expect("writing the blob to a file always works"); + } + + polkavm_path + }, + RuntimeTarget::Wasm => target_directory.join(format!("{}.wasm", blob_name)), + } } fn compact_wasm( @@ -786,7 +912,7 @@ fn compact_wasm( cargo_manifest: &Path, out_name: &str, ) -> Option { - let default_out_name = get_blob_name(cargo_manifest); + let default_out_name = get_blob_name(RuntimeTarget::Wasm, cargo_manifest); let in_path = project .join("target/wasm32-unknown-unknown") .join(inner_profile.directory()) @@ -973,6 +1099,7 @@ fn generate_rerun_if_changed_instructions( println!("cargo:rerun-if-env-changed={}", crate::WASM_TARGET_DIRECTORY); println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_TOOLCHAIN); println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_STD); + println!("cargo:rerun-if-env-changed={}", crate::RUNTIME_TARGET); } /// Track files and paths related to the given package to rerun `build.rs` on any relevant change. @@ -1018,7 +1145,7 @@ fn copy_blob_to_target_directory(cargo_manifest: &Path, blob_binary: &WasmBinary fs::copy( blob_binary.wasm_binary_path(), - target_dir.join(format!("{}.wasm", get_blob_name(cargo_manifest))), + target_dir.join(format!("{}.wasm", get_blob_name(RuntimeTarget::Wasm, cargo_manifest))), ) .expect("Copies blob binary to `WASM_TARGET_DIRECTORY`."); }